Repository: IUrreta/DatabaseEditor Branch: release Commit: 1dc9d243130d Files: 104 Total size: 4.0 MB Directory structure: gitextract_craxx02i/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── nightly.yml ├── .gitignore ├── LICENSE ├── README.md ├── api/ │ ├── ask-openai.js │ ├── auth/ │ │ └── patreon/ │ │ ├── login.js │ │ ├── logout.js │ │ └── verify.js │ ├── check-cookie.js │ ├── me.js │ └── usage-today.js ├── assets/ │ ├── custom/ │ │ └── .gitkeep │ └── fonts/ │ ├── NewNumbers.otf │ ├── NumbersBold.otf │ └── NumbersRegular.otf ├── lib/ │ ├── accessControl.js │ ├── getUserTierServer.js │ ├── rateLimits.js │ └── redis.js ├── package.json ├── src/ │ ├── data/ │ │ ├── 2025_changes.json │ │ ├── 2026_changes.json │ │ ├── contracts_2025.json │ │ ├── members.json │ │ ├── news/ │ │ │ ├── news_prompts_templates.json │ │ │ ├── news_titles_templates.json │ │ │ ├── turning_points_prompts_templates.json │ │ │ └── turning_points_titles_templates.json │ │ ├── nightly_patch_notes.md │ │ ├── records.json │ │ └── tables_2026.json │ ├── index.html │ ├── index.js │ ├── js/ │ │ ├── backend/ │ │ │ ├── UESaveHandler.js │ │ │ ├── UESaveTool/ │ │ │ │ ├── Gvas.js │ │ │ │ ├── GvasHeader.js │ │ │ │ ├── LICENSE │ │ │ │ ├── PropertyErrors.js │ │ │ │ ├── Serializer.js │ │ │ │ ├── arrays/ │ │ │ │ │ ├── IntArray.js │ │ │ │ │ ├── SoftObjectArray.js │ │ │ │ │ ├── StructArray.js │ │ │ │ │ └── index.js │ │ │ │ ├── factories.js │ │ │ │ ├── index.js │ │ │ │ └── properties/ │ │ │ │ ├── ArrayProperty.js │ │ │ │ ├── BoolProperty.js │ │ │ │ ├── EnumProperty.js │ │ │ │ ├── FloatProperty.js │ │ │ │ ├── Guid.js │ │ │ │ ├── Int16Property.js │ │ │ │ ├── Int64Property.js │ │ │ │ ├── Int8Property.js │ │ │ │ ├── IntProperty.js │ │ │ │ ├── ObjectProperty.js │ │ │ │ ├── Property.js │ │ │ │ ├── SoftObjectProperty.js │ │ │ │ ├── StrProperty.js │ │ │ │ ├── StrProperty_.js │ │ │ │ ├── StructProperty.js │ │ │ │ ├── Tuple.js │ │ │ │ └── index.js │ │ │ ├── command.js │ │ │ ├── commandGlobals.js │ │ │ ├── dbManager.js │ │ │ ├── scriptUtils/ │ │ │ │ ├── calendarUtils.js │ │ │ │ ├── carAnalysisUtils.js │ │ │ │ ├── carConstants.js │ │ │ │ ├── countries.js │ │ │ │ ├── createStaffUtils.js │ │ │ │ ├── dbUtils.js │ │ │ │ ├── editTeamUtils.js │ │ │ │ ├── eidtStatsUtils.js │ │ │ │ ├── head2head.js │ │ │ │ ├── modUtils.js │ │ │ │ ├── newsUtils.js │ │ │ │ ├── recordUtils.js │ │ │ │ ├── regulationsUtils.js │ │ │ │ ├── transferUtils.js │ │ │ │ └── triggerUtils.js │ │ │ └── worker.js │ │ └── frontend/ │ │ ├── calendar.js │ │ ├── config.js │ │ ├── devTools.js │ │ ├── dragFile.js │ │ ├── head2head.js │ │ ├── news.js │ │ ├── performance.js │ │ ├── recentsManager.js │ │ ├── regulations.js │ │ ├── renderer.js │ │ ├── seasonMods.js │ │ ├── seasonViewer.js │ │ ├── stats.js │ │ ├── teamReplacements.js │ │ ├── teams.js │ │ └── transfers.js │ ├── styles.css │ └── themes.css ├── vercel.json └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: f1dbeditor open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Save file and log.txt** Attach a link to any file transfer web where you sharen your save and log.txt (located in the same folder as your save in the DBEditor) so I can try to reproduce the bug **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "[FEATURE]" labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/workflows/nightly.yml ================================================ name: Nightly Vercel Deploy on: schedule: - cron: '0 2 * * *' # 02:00 UTC → 03:00/04:00 en Madrid según DST workflow_dispatch: # para probarlo a mano jobs: trigger: runs-on: ubuntu-latest steps: - name: Trigger Vercel Deploy Hook run: curl -sSf -X POST "$VERCEL_DEPLOY_HOOK_URL" env: VERCEL_DEPLOY_HOOK_URL: ${{ secrets.VERCEL_DEPLOY_HOOK_URL }} ================================================ FILE: .gitignore ================================================ node_modules result licenses backup log.txt **/build/back **/__pycache__ *.sav configs/*.json DBEditor assets/custom/*.png assets/custom/*.jpg assets/custom/*.jpeg !base24_config.json dist/ /patreon/ *.txt resize_images.js .vscode/ test/ .env .vercel/ .env*.local AGENTS.md *.tsx .vercel scripts/ ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ Copia de DATABASE EDITOR F1 MANAGER 23

OPEN SOURCE TOOL TO EDIT YOUR SAVE FILES FROM F1 MANAGER 23 & 24

Supports every bit of cuistomization you can imagine, and even more

[![Discord](https://img.shields.io/badge/Discord-Community-5865F2?style=flat-square&logo=discord&logoColor=white)](https://discord.gg/3QXg3hsD8B) [![Patreon](https://img.shields.io/badge/Patreon-Support%20the%20project-F96854?style=flat-square&logo=patreon&logoColor=white)](https://www.patreon.com/f1dbeditor) [![GitHub Release](https://img.shields.io/github/v/release/IUrreta/DatabaseEditor?style=flat-square&logo=github&logoColor=white)](https://github.com/IUrreta/DatabaseEditor/releases/latest) [![GitHub Stars](https://img.shields.io/github/stars/IUrreta/DatabaseEditor?style=flat-square&logo=github&logoColor=white)](https://github.com/IUrreta/DatabaseEditor/stargazers) ## Features Overview ### Driver & Staff Management - **Transfers** between teams for both drivers and staff - **Edit contracts**, including future deals and salaries - **View pre-contracts** at a glance with icons - **Edit stats**, mentality, and marketability - **Rename** any driver or staff member (even abbreviations) - **Compare** two drivers or staff members side-by-side - **Visualize** next year's grid


--- ### Season & Calendar Customization - **Edit race calendar**, including race order and weather - **Difficulty presets** — make your save harder with adjustable challenges - **Freeze mentality system** if you don’t want it affecting your gameplay - **Fix AI not refurbishing facilities** - **Change team** at any moment

> [!CAUTION] > For editing the **order or number of races**, it’s still recommended to do it before the first race of the season. > You can safely edit race weather anytime. --- ### Car & Engine Management - **Edit every car stat** from all teams, including **espertise** - **View performance evolution** across the season and details in all areas - **Compare performance** attributes with graphs - **Edit or add new engine suppliers**


--- ### Team Management - Edit **facilities**, **budget cap**, **objectives**, **pit crew**, and more - Change **engine supplier** instantly

--- ### Career History & Stats - View past seasons in a **Wikipedia-style table** (including F2/F3) - Check **career records** (wins, poles, podiums, WDCs, points...) - See **all details at once** with Season Review - See **every session result** and **edit race results** - Compare **drivers and teams** through detailed **Head-to-Head** charts

--- ### News System & Turning Points - Follow your career’s storylines through the **News Tab** - **Decide outcomes** of major events with the new **Turning Points** system - All news and outcomes are stored directly in your save file


--- ### Seasonal Mods Update your freshly created save to the 2025 or 2026 seasons with a complete database overhaul with **driver transfers**, **car and engine performance**, **driver ratings**, **calendars**, **regulations** and even **FREE TURNING POINTS** for engine development in the 2026 Season Update! (also available in Settings -> Editor)

--- ### Credits - [xAranaktu for the Save Repacker](https://github.com/xAranaktu/F1-Manager-2022-SaveFile-Repacker) - F1 Dark font: Font used under CC BY 4.0 Source: https://www.onlinewebfonts.com ================================================ FILE: api/ask-openai.js ================================================ import OpenAI from "openai"; import { getUserTierServer } from "../lib/getUserTierServer.js"; import { getDailyLimitForTier } from "../lib/rateLimits.js"; import { redis } from "../lib/redis.js"; const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export default async function handler(req, res) { try { const body = typeof req.body === "string" ? JSON.parse(req.body) : req.body; const { messages, max_tokens } = body; // 1️⃣ Auth const user = getUserTierServer(req); if (!user.isLoggedIn) { return res.status(401).json({ error: "Not logged in" }); } const tier = user.tier; const userId = user.id; // 2️⃣ Limit desde ENV const limit = getDailyLimitForTier(tier); // 3️⃣ Redis key const today = new Date().toISOString().slice(0, 10); const redisKey = `ratelimit:${userId}:${today}`; let used = Number(await redis.get(redisKey)) || 0; // 4️⃣ Rate limit if (used >= limit) { return res.status(429).json({ error: "Daily limit reached", }); } // 5️⃣ Incremento await redis.incr(redisKey); await redis.expire(redisKey, 60 * 60 * 24); // 6️⃣ OpenAI let aiModel = "gpt-5-mini"; if (tier === "Backer") { aiModel = "gpt-5-nano"; } else if (tier === "Insider") { const firstHalfLimit = Math.ceil(limit / 2); aiModel = used < firstHalfLimit ? "gpt-5-mini" : "gpt-5-nano"; } const safeMaxTokens = Math.min(max_tokens || 1500, 4000); const input = messages.map(m => ({ role: m.role, content: m.content })); const response = await client.responses.create({ model: aiModel, input, max_output_tokens: safeMaxTokens, reasoning: {"effort": "low"} }); const text = response.output_text || ""; return res.status(200).json({ text, used: used + 1, limit }); } catch (err) { console.error("OpenAI API error:", err); return res.status(500).json({ error: err.message }); } } ================================================ FILE: api/auth/patreon/login.js ================================================ export default function handler(req, res) { const { PATREON_CLIENT_ID, PATREON_REDIRECT_URI } = process.env; if (!PATREON_CLIENT_ID || !PATREON_REDIRECT_URI) { return res.status(500).json({ error: 'Missing Patreon environment variables' }); } // Scopes: identity (profile), identity.memberships (campaign info) const scopes = 'identity identity.memberships'; const redirectUrl = `https://www.patreon.com/oauth2/authorize?response_type=code&client_id=${PATREON_CLIENT_ID}&redirect_uri=${encodeURIComponent(PATREON_REDIRECT_URI)}&scope=${encodeURIComponent(scopes)}`; res.redirect(redirectUrl); } ================================================ FILE: api/auth/patreon/logout.js ================================================ import { serialize } from 'cookie'; export default function handler(req, res) { const cookie = serialize('auth_token', '', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', path: '/', maxAge: -1, }); res.setHeader('Set-Cookie', cookie); res.status(200).json({ success: true, message: 'Logged out successfully' }); } ================================================ FILE: api/auth/patreon/verify.js ================================================ import jwt from 'jsonwebtoken'; import { serialize } from 'cookie'; import { getEffectiveTier } from '../../../lib/accessControl.js'; export default async function handler(req, res) { const { code } = req.query; const { PATREON_CLIENT_ID, PATREON_CLIENT_SECRET, PATREON_REDIRECT_URI } = process.env; if (!code) { return res.status(400).json({ error: 'Missing code parameter' }); } if (!PATREON_CLIENT_ID || !PATREON_CLIENT_SECRET || !PATREON_REDIRECT_URI) { return res.status(500).json({ error: 'Missing Patreon environment variables' }); } try { // 1. Exchange code for access token const tokenResponse = await fetch('https://www.patreon.com/api/oauth2/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ code, grant_type: 'authorization_code', client_id: PATREON_CLIENT_ID, client_secret: PATREON_CLIENT_SECRET, redirect_uri: PATREON_REDIRECT_URI, }), }); const tokenData = await tokenResponse.json(); if (!tokenResponse.ok) { console.error('Token exchange failed:', tokenData); return res.status(tokenResponse.status).json({ error: 'Failed to exchange token', details: tokenData }); } const accessToken = tokenData.access_token; // 2. Fetch user identity and memberships // Using API v2 const identityResponse = await fetch('https://www.patreon.com/api/oauth2/v2/identity?include=memberships.currently_entitled_tiers&fields%5Buser%5D=full_name,thumb_url&fields%5Bmember%5D=patron_status,currently_entitled_amount_cents&fields%5Btier%5D=title', { headers: { Authorization: `Bearer ${accessToken}`, }, }); const identityData = await identityResponse.json(); if (!identityResponse.ok) { console.error('Identity fetch failed:', identityData); return res.status(identityResponse.status).json({ error: 'Failed to fetch identity', details: identityData }); } // 3. Parse membership data to find tier const memberships = identityData.included || []; let isMember = false; let tier = 'Free'; let amountCents = 0; let tierName = 'None'; const tierIDs = ["25157070", "25124139", "25132338"]; const paidTiers = ["Backer", "Insider", "Founder"]; const patronStatusOrder = { "None": 0, "Free": 0, "Backer": 1, "Insider": 2, "Founder": 3 }; // Logic to determine tier based on memberships for (const item of memberships) { if (item.type === 'member' && item.attributes.patron_status === 'active_patron') { isMember = true; amountCents = item.attributes.currently_entitled_amount_cents; } if (item.type === 'tier' && tierIDs.includes(item.id)) { //get the highest, order is backer -> insider -> founder if (patronStatusOrder[item.attributes.title] > patronStatusOrder[tierName]) { tierName = item.attributes.title; } } } if (isMember) { tier = tierName; } const fullName = identityData?.data?.attributes?.full_name || ""; const baseTier = tier; const { tier: effectiveTier, whitelisted } = getEffectiveTier({ name: fullName, baseTier }); const isPaid = paidTiers.includes(effectiveTier); const tierNumbers = { "Free": 0, "Backer": 1, "Insider": 2, "Founder": 3 }; const patreonUser = { name: fullName, thumbUrl: identityData.data.attributes.thumb_url, isMember, amountCents, tier: effectiveTier, }; const token = jwt.sign( { name: patreonUser.name, tier: baseTier, patreonId: identityData.data.id }, process.env.JWT_SECRET, { expiresIn: '30d' } ); const serializedCookie = serialize('auth_token', token, { httpOnly: true, // VITAL: Browser JS cannot read this secure: process.env.NODE_ENV === 'production', // HTTPS only sameSite: 'strict', maxAge: 60 * 60 * 24 * 30, // 30 days path: '/' }); res.setHeader('Set-Cookie', serializedCookie); return res.status(200).json({ success: true, user: { fullName: patreonUser.name }, tier: patreonUser.tier, tierNumber: tierNumbers[patreonUser.tier] ?? 0, whitelisted, isLoggedIn: true, paidMember: isPaid }); } catch (error) { console.error('Server error:', error); return res.status(500).json({ error: 'Internal server error', message: error.message }); } } ================================================ FILE: api/check-cookie.js ================================================ import jwt from "jsonwebtoken"; import { serialize } from "cookie"; export default function handler(req, res) { const { auth_token } = req.cookies || {}; if (!auth_token) { // No cookie present: user is simply not logged in; no redirect needed. return res.status(200).json({ ok: true, hasCookie: false, valid: true }); } try { const decoded = jwt.verify(auth_token, process.env.JWT_SECRET); if (!decoded.patreonId) { // Delete cookie const del = serialize("auth_token", "", { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "strict", expires: new Date(0), path: "/", }); res.setHeader("Set-Cookie", del); return res.status(200).json({ ok: true, hasCookie: true, valid: false }); } return res.status(200).json({ ok: true, hasCookie: true, valid: true }); } catch (err) { // Bad cookie → borrar también const del = serialize("auth_token", "", { httpOnly: true, secure: process.env.NODE_ENV === "production", sameSite: "strict", expires: new Date(0), path: "/", }); res.setHeader("Set-Cookie", del); return res.status(200).json({ ok: true, hasCookie: true, valid: false }); } } ================================================ FILE: api/me.js ================================================ import jwt from 'jsonwebtoken'; import { getEffectiveTier } from '../lib/accessControl.js'; export default function handler(req, res) { // Vercel parses the cookie string automatically into req.cookies const { auth_token } = req.cookies; if (!auth_token) { return res.json({ isLoggedIn: false, paidMember: false, tier: 'Free', whitelisted: false }); } try { // Verify the token signature const decoded = jwt.verify(auth_token, process.env.JWT_SECRET); const paidTiers = ["Backer", "Insider", "Founder"]; const { tier, whitelisted } = getEffectiveTier({ name: decoded.name, baseTier: decoded.tier }); const isPaid = paidTiers.includes(tier); const tierNumbers = { "Free": 0, "Backer": 1, "Insider": 2, "Founder": 3 } return res.json({ isLoggedIn: true, paidMember: isPaid, tier: tier, tierNumber: tierNumbers[tier] ?? 0, whitelisted, user: { fullName: decoded.name }, }); } catch (err) { // Token is invalid or expired return res.json({ isLoggedIn: false, paidMember: false, tier: 'Free', whitelisted: false }); } } ================================================ FILE: api/usage-today.js ================================================ import { getUserTierServer } from "../lib/getUserTierServer.js"; import { getDailyLimitForTier } from "../lib/rateLimits.js"; import { redis } from "../lib/redis.js"; export default async function handler(req, res) { try { const user = getUserTierServer(req); if (!user.isLoggedIn) { return res.status(401).json({ error: "Not logged in" }); } const userId = user.id; const tier = user.tier; const limit = getDailyLimitForTier(tier); const today = new Date().toISOString().slice(0, 10); const redisKey = `ratelimit:${userId}:${today}`; let used = await redis.get(redisKey); if (!used) used = 0; used = Number(used); const percentage = limit > 0 ? Math.min(100, Math.round((used / limit) * 100)) : 0; return res.status(200).json({ used, limit, percentage }); } catch (err) { console.error("Usage endpoint error:", err); return res.status(500).json({ error: err.message }); } } ================================================ FILE: assets/custom/.gitkeep ================================================ ================================================ FILE: lib/accessControl.js ================================================ const TIER_ORDER = { Free: 0, Backer: 1, Insider: 2, Founder: 3, }; function normalizeName(name) { return String(name || "").trim().toLowerCase(); } function parseEnvList(value) { if (!value) return new Set(); return new Set( String(value) .split(/[,\n]/g) .map((item) => normalizeName(item)) .filter(Boolean) ); } function maxTier(a, b) { const aOrder = TIER_ORDER[a] ?? -1; const bOrder = TIER_ORDER[b] ?? -1; return aOrder >= bOrder ? a : b; } /** * Applies server-side access overrides controlled by env vars. * * Env vars: * - DEVELOPER_NAME: exact Patreon `full_name` to force "Founder" * - INSIDER_WHITELIST: comma/newline-separated names to grant at least "Founder" */ export function getEffectiveTier({ name, baseTier }) { const normalizedUserName = normalizeName(name); let tier = baseTier || "Free"; let whitelisted = false; const developerName = normalizeName(process.env.DEVELOPER_NAME); if (developerName && normalizedUserName === developerName) { return { tier: "Founder", whitelisted: false }; } const insiderWhitelist = parseEnvList(process.env.INSIDER_WHITELIST); if (insiderWhitelist.has(normalizedUserName)) { tier = maxTier(tier, "Founder"); whitelisted = true; } return { tier, whitelisted }; } ================================================ FILE: lib/getUserTierServer.js ================================================ import jwt from "jsonwebtoken"; import { getEffectiveTier } from "./accessControl.js"; export function getUserTierServer(req) { const { auth_token } = req.cookies || {}; if (!auth_token) { return { isLoggedIn: false, paidMember: false, tier: "Free", whitelisted: false, user: null, id: null }; } try { const decoded = jwt.verify(auth_token, process.env.JWT_SECRET); const paidTiers = ["Backer", "Insider", "Founder"]; const { tier, whitelisted } = getEffectiveTier({ name: decoded.name, baseTier: decoded.tier }); const isPaid = paidTiers.includes(tier); return { isLoggedIn: true, paidMember: isPaid, tier, whitelisted, user: { fullName: decoded.name }, id: decoded.patreonId }; } catch (err) { return { isLoggedIn: false, paidMember: false, tier: "Free", whitelisted: false, user: null, id: null }; } } ================================================ FILE: lib/rateLimits.js ================================================ export function getDailyLimitForTier(tier) { const limits = { Founder: Number(process.env.DAILY_LIMIT_FOUNDER), Insider: Number(process.env.DAILY_LIMIT_INSIDER), Backer: Number(process.env.DAILY_LIMIT_BACKER), Free: Number(process.env.DAILY_LIMIT_FREE), }; return limits[tier] ?? limits.Free; } ================================================ FILE: lib/redis.js ================================================ import { Redis } from "@upstash/redis"; export const redis = Redis.fromEnv(); ================================================ FILE: package.json ================================================ { "name": "Database Editor F1 Manager", "version": "3.5.13", "description": "A tool that will let you edit your save file from F1 Manager games", "main": "main.js", "scripts": { "build": "webpack", "optimize:logos": "node scripts/optimize-team-logos.js" }, "repository": "https://github.com/IUrreta/DatabaseEditor", "author": "IUrreta", "license": "LGPL-3.0-or-later", "dependencies": { "@upstash/redis": "^1.35.7", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", "autoprefixer": "^10.4.20", "babel-loader": "^9.2.1", "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.0", "buffer": "^6.0.3", "chart.js": "^4.4.0", "chartjs-plugin-annotation": "^3.0.1", "chartjs-plugin-datalabels": "^2.2.0", "cookie": "^1.0.2", "copy-webpack-plugin": "^12.0.2", "crypto-browserify": "^3.12.1", "css-loader": "^7.1.2", "csv-parser": "^3.2.0", "dompurify": "^3.3.0", "file-saver": "^2.0.5", "html-webpack-plugin": "^5.6.3", "idb-keyval": "^6.2.2", "image-minimizer-webpack-plugin": "^4.1.3", "interactjs": "^1.10.17", "jsonwebtoken": "^9.0.2", "lz-string": "^1.5.0", "marked": "^7.0.3", "mini-css-extract-plugin": "^2.9.2", "openai": "^6.10.0", "pako": "^2.1.0", "playwright": "^1.57.0", "postcss-loader": "^8.1.1", "sass": "^1.84.0", "sass-loader": "^16.0.4", "sharp": "^0.33.5", "simple-git": "^3.19.1", "sql.js": "^1.13.0", "style-loader": "^4.0.0", "turndown": "^7.2.2", "webpack": "^5.97.1", "webpack-cli": "^5.1.4" }, "devDependencies": { "@playwright/test": "^1.57.0" } } ================================================ FILE: src/data/2025_changes.json ================================================ { "Stats": [ { "Max": 100, "StaffID": 1, "StatID": 2, "Val": 93 }, { "Max": 100, "StaffID": 1, "StatID": 3, "Val": 95 }, { "Max": 100, "StaffID": 1, "StatID": 4, "Val": 92 }, { "Max": 100, "StaffID": 1, "StatID": 5, "Val": 90 }, { "Max": 100, "StaffID": 1, "StatID": 6, "Val": 90 }, { "Max": 100, "StaffID": 1, "StatID": 7, "Val": 90 }, { "Max": 100, "StaffID": 1, "StatID": 8, "Val": 94 }, { "Max": 100, "StaffID": 1, "StatID": 9, "Val": 86 }, { "Max": 100, "StaffID": 1, "StatID": 10, "Val": 83 }, { "Max": 100, "StaffID": 2, "StatID": 2, "Val": 96 }, { "Max": 100, "StaffID": 2, "StatID": 3, "Val": 96 }, { "Max": 100, "StaffID": 2, "StatID": 4, "Val": 89 }, { "Max": 100, "StaffID": 2, "StatID": 5, "Val": 86 }, { "Max": 100, "StaffID": 2, "StatID": 6, "Val": 89 }, { "Max": 100, "StaffID": 2, "StatID": 7, "Val": 87 }, { "Max": 100, "StaffID": 2, "StatID": 8, "Val": 91 }, { "Max": 100, "StaffID": 2, "StatID": 9, "Val": 86 }, { "Max": 100, "StaffID": 2, "StatID": 10, "Val": 85 }, { "Max": 100, "StaffID": 3, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 3, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 3, "StatID": 4, "Val": 85 }, { "Max": 100, "StaffID": 3, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 3, "StatID": 6, "Val": 85 }, { "Max": 100, "StaffID": 3, "StatID": 7, "Val": 84 }, { "Max": 100, "StaffID": 3, "StatID": 8, "Val": 86 }, { "Max": 100, "StaffID": 3, "StatID": 9, "Val": 87 }, { "Max": 100, "StaffID": 3, "StatID": 10, "Val": 79 }, { "Max": 100, "StaffID": 8, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 8, "StatID": 3, "Val": 82 }, { "Max": 100, "StaffID": 8, "StatID": 4, "Val": 90 }, { "Max": 100, "StaffID": 8, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 8, "StatID": 6, "Val": 72 }, { "Max": 100, "StaffID": 8, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 8, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 8, "StatID": 9, "Val": 82 }, { "Max": 100, "StaffID": 8, "StatID": 10, "Val": 89 }, { "Max": 100, "StaffID": 10, "StatID": 2, "Val": 96 }, { "Max": 100, "StaffID": 10, "StatID": 3, "Val": 97 }, { "Max": 100, "StaffID": 10, "StatID": 4, "Val": 92 }, { "Max": 100, "StaffID": 10, "StatID": 5, "Val": 89 }, { "Max": 100, "StaffID": 10, "StatID": 6, "Val": 91 }, { "Max": 100, "StaffID": 10, "StatID": 7, "Val": 92 }, { "Max": 100, "StaffID": 10, "StatID": 8, "Val": 98 }, { "Max": 100, "StaffID": 10, "StatID": 9, "Val": 92 }, { "Max": 100, "StaffID": 10, "StatID": 10, "Val": 87 }, { "Max": 100, "StaffID": 11, "StatID": 2, "Val": 90 }, { "Max": 100, "StaffID": 11, "StatID": 3, "Val": 92 }, { "Max": 100, "StaffID": 11, "StatID": 4, "Val": 86 }, { "Max": 100, "StaffID": 11, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 11, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 11, "StatID": 7, "Val": 90 }, { "Max": 100, "StaffID": 11, "StatID": 8, "Val": 94 }, { "Max": 100, "StaffID": 11, "StatID": 9, "Val": 87 }, { "Max": 100, "StaffID": 11, "StatID": 10, "Val": 86 }, { "Max": 100, "StaffID": 12, "StatID": 2, "Val": 88 }, { "Max": 100, "StaffID": 12, "StatID": 3, "Val": 89 }, { "Max": 100, "StaffID": 12, "StatID": 4, "Val": 96 }, { "Max": 100, "StaffID": 12, "StatID": 5, "Val": 95 }, { "Max": 100, "StaffID": 12, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 12, "StatID": 7, "Val": 86 }, { "Max": 100, "StaffID": 12, "StatID": 8, "Val": 87 }, { "Max": 100, "StaffID": 12, "StatID": 9, "Val": 85 }, { "Max": 100, "StaffID": 12, "StatID": 10, "Val": 80 }, { "Max": 100, "StaffID": 13, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 13, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 13, "StatID": 4, "Val": 82 }, { "Max": 100, "StaffID": 13, "StatID": 5, "Val": 86 }, { "Max": 100, "StaffID": 13, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 13, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 13, "StatID": 8, "Val": 82 }, { "Max": 100, "StaffID": 13, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 13, "StatID": 10, "Val": 83 }, { "Max": 100, "StaffID": 14, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 14, "StatID": 3, "Val": 77 }, { "Max": 100, "StaffID": 14, "StatID": 4, "Val": 84 }, { "Max": 100, "StaffID": 14, "StatID": 5, "Val": 83 }, { "Max": 100, "StaffID": 14, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 14, "StatID": 7, "Val": 83 }, { "Max": 100, "StaffID": 14, "StatID": 8, "Val": 84 }, { "Max": 100, "StaffID": 14, "StatID": 9, "Val": 78 }, { "Max": 100, "StaffID": 14, "StatID": 10, "Val": 93 }, { "Max": 100, "StaffID": 15, "StatID": 2, "Val": 84 }, { "Max": 100, "StaffID": 15, "StatID": 3, "Val": 83 }, { "Max": 100, "StaffID": 15, "StatID": 4, "Val": 91 }, { "Max": 100, "StaffID": 15, "StatID": 5, "Val": 88 }, { "Max": 100, "StaffID": 15, "StatID": 6, "Val": 93 }, { "Max": 100, "StaffID": 15, "StatID": 7, "Val": 83 }, { "Max": 100, "StaffID": 15, "StatID": 8, "Val": 82 }, { "Max": 100, "StaffID": 15, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 15, "StatID": 10, "Val": 76 }, { "Max": 100, "StaffID": 17, "StatID": 2, "Val": 82 }, { "Max": 100, "StaffID": 17, "StatID": 3, "Val": 82 }, { "Max": 100, "StaffID": 17, "StatID": 4, "Val": 87 }, { "Max": 100, "StaffID": 17, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 17, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 17, "StatID": 7, "Val": 91 }, { "Max": 100, "StaffID": 17, "StatID": 8, "Val": 84 }, { "Max": 100, "StaffID": 17, "StatID": 9, "Val": 86 }, { "Max": 100, "StaffID": 17, "StatID": 10, "Val": 82 }, { "Max": 100, "StaffID": 18, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 18, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 18, "StatID": 4, "Val": 76 }, { "Max": 100, "StaffID": 18, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 18, "StatID": 6, "Val": 92 }, { "Max": 100, "StaffID": 18, "StatID": 7, "Val": 83 }, { "Max": 100, "StaffID": 18, "StatID": 8, "Val": 81 }, { "Max": 100, "StaffID": 18, "StatID": 9, "Val": 76 }, { "Max": 100, "StaffID": 18, "StatID": 10, "Val": 85 }, { "Max": 100, "StaffID": 20, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 20, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 20, "StatID": 4, "Val": 86 }, { "Max": 100, "StaffID": 20, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 20, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 20, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 20, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 20, "StatID": 9, "Val": 79 }, { "Max": 100, "StaffID": 20, "StatID": 10, "Val": 80 }, { "Max": 100, "StaffID": 22, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 22, "StatID": 3, "Val": 76 }, { "Max": 100, "StaffID": 22, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 22, "StatID": 5, "Val": 70 }, { "Max": 100, "StaffID": 22, "StatID": 6, "Val": 80 }, { "Max": 100, "StaffID": 22, "StatID": 7, "Val": 75 }, { "Max": 100, "StaffID": 22, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 22, "StatID": 9, "Val": 75 }, { "Max": 100, "StaffID": 22, "StatID": 10, "Val": 78 }, { "Max": 100, "StaffID": 23, "StatID": 2, "Val": 85 }, { "Max": 100, "StaffID": 23, "StatID": 3, "Val": 87 }, { "Max": 100, "StaffID": 23, "StatID": 4, "Val": 88 }, { "Max": 100, "StaffID": 23, "StatID": 5, "Val": 85 }, { "Max": 100, "StaffID": 23, "StatID": 6, "Val": 96 }, { "Max": 100, "StaffID": 23, "StatID": 7, "Val": 81 }, { "Max": 100, "StaffID": 23, "StatID": 8, "Val": 86 }, { "Max": 100, "StaffID": 23, "StatID": 9, "Val": 85 }, { "Max": 100, "StaffID": 23, "StatID": 10, "Val": 92 }, { "Max": 100, "StaffID": 76, "StatID": 2, "Val": 78 }, { "Max": 100, "StaffID": 76, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 76, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 76, "StatID": 5, "Val": 82 }, { "Max": 100, "StaffID": 76, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 76, "StatID": 7, "Val": 77 }, { "Max": 100, "StaffID": 76, "StatID": 8, "Val": 81 }, { "Max": 100, "StaffID": 76, "StatID": 9, "Val": 78 }, { "Max": 100, "StaffID": 76, "StatID": 10, "Val": 86 }, { "Max": 100, "StaffID": 77, "StatID": 2, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 3, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 4, "Val": 94 }, { "Max": 100, "StaffID": 77, "StatID": 5, "Val": 87 }, { "Max": 100, "StaffID": 77, "StatID": 6, "Val": 86 }, { "Max": 100, "StaffID": 77, "StatID": 7, "Val": 91 }, { "Max": 100, "StaffID": 77, "StatID": 8, "Val": 91 }, { "Max": 100, "StaffID": 77, "StatID": 9, "Val": 86 }, { "Max": 100, "StaffID": 77, "StatID": 10, "Val": 88 }, { "Max": 100, "StaffID": 80, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 80, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 80, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 80, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 80, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 80, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 80, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 80, "StatID": 9, "Val": 75 }, { "Max": 100, "StaffID": 80, "StatID": 10, "Val": 83 }, { "Max": 100, "StaffID": 81, "StatID": 2, "Val": 83 }, { "Max": 100, "StaffID": 81, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 81, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 81, "StatID": 5, "Val": 82 }, { "Max": 100, "StaffID": 81, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 81, "StatID": 7, "Val": 85 }, { "Max": 100, "StaffID": 81, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 81, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 81, "StatID": 10, "Val": 89 }, { "Max": 100, "StaffID": 83, "StatID": 2, "Val": 82 }, { "Max": 100, "StaffID": 83, "StatID": 3, "Val": 81 }, { "Max": 100, "StaffID": 83, "StatID": 4, "Val": 88 }, { "Max": 100, "StaffID": 83, "StatID": 5, "Val": 81 }, { "Max": 100, "StaffID": 83, "StatID": 6, "Val": 86 }, { "Max": 100, "StaffID": 83, "StatID": 7, "Val": 82 }, { "Max": 100, "StaffID": 83, "StatID": 8, "Val": 85 }, { "Max": 100, "StaffID": 83, "StatID": 9, "Val": 88 }, { "Max": 100, "StaffID": 83, "StatID": 10, "Val": 85 }, { "Max": 100, "StaffID": 95, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 95, "StatID": 3, "Val": 85 }, { "Max": 100, "StaffID": 95, "StatID": 4, "Val": 86 }, { "Max": 100, "StaffID": 95, "StatID": 5, "Val": 71 }, { "Max": 100, "StaffID": 95, "StatID": 6, "Val": 79 }, { "Max": 100, "StaffID": 95, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 95, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 95, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 95, "StatID": 10, "Val": 82 }, { "Max": 100, "StaffID": 99, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 99, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 99, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 99, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 99, "StatID": 6, "Val": 79 }, { "Max": 100, "StaffID": 99, "StatID": 7, "Val": 70 }, { "Max": 100, "StaffID": 99, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 99, "StatID": 9, "Val": 63 }, { "Max": 100, "StaffID": 99, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 102, "StatID": 2, "Val": 83 }, { "Max": 100, "StaffID": 102, "StatID": 3, "Val": 86 }, { "Max": 100, "StaffID": 102, "StatID": 4, "Val": 90 }, { "Max": 100, "StaffID": 102, "StatID": 5, "Val": 87 }, { "Max": 100, "StaffID": 102, "StatID": 6, "Val": 88 }, { "Max": 100, "StaffID": 102, "StatID": 7, "Val": 84 }, { "Max": 100, "StaffID": 102, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 102, "StatID": 9, "Val": 85 }, { "Max": 100, "StaffID": 102, "StatID": 10, "Val": 85 }, { "Max": 100, "StaffID": 105, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 105, "StatID": 4, "Val": 84 }, { "Max": 100, "StaffID": 105, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 105, "StatID": 7, "Val": 81 }, { "Max": 100, "StaffID": 105, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 9, "Val": 77 }, { "Max": 100, "StaffID": 105, "StatID": 10, "Val": 77 }, { "Max": 100, "StaffID": 106, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 106, "StatID": 3, "Val": 80 }, { "Max": 100, "StaffID": 106, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 106, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 106, "StatID": 6, "Val": 79 }, { "Max": 100, "StaffID": 106, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 106, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 106, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 106, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 107, "StatID": 2, "Val": 72 }, { "Max": 100, "StaffID": 107, "StatID": 3, "Val": 81 }, { "Max": 100, "StaffID": 107, "StatID": 4, "Val": 78 }, { "Max": 100, "StaffID": 107, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 107, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 107, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 107, "StatID": 8, "Val": 79 }, { "Max": 100, "StaffID": 107, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 107, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 109, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 109, "StatID": 3, "Val": 67 }, { "Max": 100, "StaffID": 109, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 109, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 109, "StatID": 6, "Val": 72 }, { "Max": 100, "StaffID": 109, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 109, "StatID": 8, "Val": 65 }, { "Max": 100, "StaffID": 109, "StatID": 9, "Val": 70 }, { "Max": 100, "StaffID": 109, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 116, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 116, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 116, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 116, "StatID": 5, "Val": 71 }, { "Max": 100, "StaffID": 116, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 116, "StatID": 7, "Val": 71 }, { "Max": 100, "StaffID": 116, "StatID": 8, "Val": 76 }, { "Max": 100, "StaffID": 116, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 116, "StatID": 10, "Val": 81 }, { "Max": 100, "StaffID": 119, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 119, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 119, "StatID": 4, "Val": 76 }, { "Max": 100, "StaffID": 119, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 119, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 119, "StatID": 7, "Val": 63 }, { "Max": 100, "StaffID": 119, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 119, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 119, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 120, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 120, "StatID": 3, "Val": 71 }, { "Max": 100, "StaffID": 120, "StatID": 4, "Val": 76 }, { "Max": 100, "StaffID": 120, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 120, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 120, "StatID": 7, "Val": 68 }, { "Max": 100, "StaffID": 120, "StatID": 8, "Val": 76 }, { "Max": 100, "StaffID": 120, "StatID": 9, "Val": 77 }, { "Max": 100, "StaffID": 120, "StatID": 10, "Val": 73 }, { "Max": 100, "StaffID": 121, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 121, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 121, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 121, "StatID": 5, "Val": 68 }, { "Max": 100, "StaffID": 121, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 121, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 121, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 121, "StatID": 9, "Val": 72 }, { "Max": 100, "StaffID": 121, "StatID": 10, "Val": 70 }, { "Max": 100, "StaffID": 123, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 123, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 123, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 123, "StatID": 5, "Val": 72 }, { "Max": 100, "StaffID": 123, "StatID": 6, "Val": 69 }, { "Max": 100, "StaffID": 123, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 123, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 123, "StatID": 9, "Val": 65 }, { "Max": 100, "StaffID": 123, "StatID": 10, "Val": 62 }, { "Max": 100, "StaffID": 127, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 127, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 127, "StatID": 4, "Val": 69 }, { "Max": 100, "StaffID": 127, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 127, "StatID": 6, "Val": 70 }, { "Max": 100, "StaffID": 127, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 127, "StatID": 8, "Val": 63 }, { "Max": 100, "StaffID": 127, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 127, "StatID": 10, "Val": 76 }, { "Max": 100, "StaffID": 130, "StatID": 2, "Val": 72 }, { "Max": 100, "StaffID": 130, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 130, "StatID": 4, "Val": 82 }, { "Max": 100, "StaffID": 130, "StatID": 5, "Val": 70 }, { "Max": 100, "StaffID": 130, "StatID": 6, "Val": 72 }, { "Max": 100, "StaffID": 130, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 130, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 130, "StatID": 9, "Val": 72 }, { "Max": 100, "StaffID": 130, "StatID": 10, "Val": 74 }, { "Max": 100, "StaffID": 135, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 135, "StatID": 3, "Val": 76 }, { "Max": 100, "StaffID": 135, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 135, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 135, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 135, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 9, "Val": 83 }, { "Max": 100, "StaffID": 135, "StatID": 10, "Val": 72 }, { "Max": 100, "StaffID": 142, "StatID": 2, "Val": 86 }, { "Max": 100, "StaffID": 142, "StatID": 3, "Val": 83 }, { "Max": 100, "StaffID": 142, "StatID": 4, "Val": 81 }, { "Max": 100, "StaffID": 142, "StatID": 5, "Val": 71 }, { "Max": 100, "StaffID": 142, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 142, "StatID": 7, "Val": 80 }, { "Max": 100, "StaffID": 142, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 142, "StatID": 9, "Val": 87 }, { "Max": 100, "StaffID": 142, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 144, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 144, "StatID": 3, "Val": 72 }, { "Max": 100, "StaffID": 144, "StatID": 4, "Val": 83 }, { "Max": 100, "StaffID": 144, "StatID": 5, "Val": 76 }, { "Max": 100, "StaffID": 144, "StatID": 6, "Val": 72 }, { "Max": 100, "StaffID": 144, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 144, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 144, "StatID": 9, "Val": 73 }, { "Max": 100, "StaffID": 144, "StatID": 10, "Val": 72 }, { "Max": 100, "StaffID": 242, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 242, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 242, "StatID": 4, "Val": 86 }, { "Max": 100, "StaffID": 242, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 242, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 242, "StatID": 7, "Val": 71 }, { "Max": 100, "StaffID": 242, "StatID": 8, "Val": 77 }, { "Max": 100, "StaffID": 242, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 242, "StatID": 10, "Val": 73 }, { "Max": 100, "StaffID": 245, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 245, "StatID": 3, "Val": 72 }, { "Max": 100, "StaffID": 245, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 245, "StatID": 5, "Val": 72 }, { "Max": 100, "StaffID": 245, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 245, "StatID": 7, "Val": 70 }, { "Max": 100, "StaffID": 245, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 245, "StatID": 9, "Val": 70 }, { "Max": 100, "StaffID": 245, "StatID": 10, "Val": 70 }, { "Max": 100, "StaffID": 248, "StatID": 2, "Val": 71 }, { "Max": 100, "StaffID": 248, "StatID": 3, "Val": 77 }, { "Max": 100, "StaffID": 248, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 248, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 248, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 248, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 248, "StatID": 8, "Val": 81 }, { "Max": 100, "StaffID": 248, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 248, "StatID": 10, "Val": 82 }, { "Max": 100, "StaffID": 252, "StatID": 2, "Val": 71 }, { "Max": 100, "StaffID": 252, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 252, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 252, "StatID": 5, "Val": 72 }, { "Max": 100, "StaffID": 252, "StatID": 6, "Val": 66 }, { "Max": 100, "StaffID": 252, "StatID": 7, "Val": 68 }, { "Max": 100, "StaffID": 252, "StatID": 8, "Val": 69 }, { "Max": 100, "StaffID": 252, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 252, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 255, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 255, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 255, "StatID": 4, "Val": 83 }, { "Max": 100, "StaffID": 255, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 255, "StatID": 6, "Val": 91 }, { "Max": 100, "StaffID": 255, "StatID": 7, "Val": 79 }, { "Max": 100, "StaffID": 255, "StatID": 8, "Val": 76 }, { "Max": 100, "StaffID": 255, "StatID": 9, "Val": 83 }, { "Max": 100, "StaffID": 255, "StatID": 10, "Val": 79 }, { "Max": 100, "StaffID": 279, "StatID": 2, "Val": 77 }, { "Max": 100, "StaffID": 279, "StatID": 3, "Val": 77 }, { "Max": 100, "StaffID": 279, "StatID": 4, "Val": 78 }, { "Max": 100, "StaffID": 279, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 279, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 279, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 279, "StatID": 8, "Val": 81 }, { "Max": 100, "StaffID": 279, "StatID": 9, "Val": 77 }, { "Max": 100, "StaffID": 279, "StatID": 10, "Val": 83 }, { "Max": 100, "StaffID": 280, "StatID": 2, "Val": 78 }, { "Max": 100, "StaffID": 280, "StatID": 3, "Val": 76 }, { "Max": 100, "StaffID": 280, "StatID": 4, "Val": 72 }, { "Max": 100, "StaffID": 280, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 280, "StatID": 6, "Val": 77 }, { "Max": 100, "StaffID": 280, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 280, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 280, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 280, "StatID": 10, "Val": 55 }, { "Max": 100, "StaffID": 281, "StatID": 2, "Val": 77 }, { "Max": 100, "StaffID": 281, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 281, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 281, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 281, "StatID": 6, "Val": 77 }, { "Max": 100, "StaffID": 281, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 281, "StatID": 8, "Val": 78 }, { "Max": 100, "StaffID": 281, "StatID": 9, "Val": 79 }, { "Max": 100, "StaffID": 281, "StatID": 10, "Val": 76 }, { "Max": 100, "StaffID": 282, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 282, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 282, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 282, "StatID": 5, "Val": 66 }, { "Max": 100, "StaffID": 282, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 282, "StatID": 7, "Val": 59 }, { "Max": 100, "StaffID": 282, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 282, "StatID": 9, "Val": 71 }, { "Max": 100, "StaffID": 282, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 283, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 283, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 283, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 283, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 283, "StatID": 6, "Val": 56 }, { "Max": 100, "StaffID": 283, "StatID": 7, "Val": 51 }, { "Max": 100, "StaffID": 283, "StatID": 8, "Val": 64 }, { "Max": 100, "StaffID": 283, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 283, "StatID": 10, "Val": 55 }, { "Max": 100, "StaffID": 284, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 284, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 284, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 284, "StatID": 5, "Val": 68 }, { "Max": 100, "StaffID": 284, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 284, "StatID": 7, "Val": 55 }, { "Max": 100, "StaffID": 284, "StatID": 8, "Val": 77 }, { "Max": 100, "StaffID": 284, "StatID": 9, "Val": 73 }, { "Max": 100, "StaffID": 284, "StatID": 10, "Val": 65 }, { "Max": 100, "StaffID": 285, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 285, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 285, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 285, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 285, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 285, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 285, "StatID": 8, "Val": 81 }, { "Max": 100, "StaffID": 285, "StatID": 9, "Val": 75 }, { "Max": 100, "StaffID": 285, "StatID": 10, "Val": 80 }, { "Max": 100, "StaffID": 286, "StatID": 2, "Val": 61 }, { "Max": 100, "StaffID": 286, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 286, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 286, "StatID": 5, "Val": 56 }, { "Max": 100, "StaffID": 286, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 286, "StatID": 7, "Val": 57 }, { "Max": 100, "StaffID": 286, "StatID": 8, "Val": 59 }, { "Max": 100, "StaffID": 286, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 286, "StatID": 10, "Val": 73 }, { "Max": 100, "StaffID": 288, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 288, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 288, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 288, "StatID": 5, "Val": 56 }, { "Max": 100, "StaffID": 288, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 288, "StatID": 7, "Val": 53 }, { "Max": 100, "StaffID": 288, "StatID": 8, "Val": 65 }, { "Max": 100, "StaffID": 288, "StatID": 9, "Val": 65 }, { "Max": 100, "StaffID": 288, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 289, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 289, "StatID": 3, "Val": 72 }, { "Max": 100, "StaffID": 289, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 289, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 289, "StatID": 6, "Val": 73 }, { "Max": 100, "StaffID": 289, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 289, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 289, "StatID": 9, "Val": 55 }, { "Max": 100, "StaffID": 289, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 301, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 301, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 301, "StatID": 4, "Val": 72 }, { "Max": 100, "StaffID": 301, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 301, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 301, "StatID": 7, "Val": 51 }, { "Max": 100, "StaffID": 301, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 301, "StatID": 9, "Val": 47 }, { "Max": 100, "StaffID": 301, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 305, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 305, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 305, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 305, "StatID": 5, "Val": 71 }, { "Max": 100, "StaffID": 305, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 305, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 305, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 305, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 305, "StatID": 10, "Val": 56 }, { "Max": 100, "StaffID": 306, "StatID": 2, "Val": 60 }, { "Max": 100, "StaffID": 306, "StatID": 3, "Val": 57 }, { "Max": 100, "StaffID": 306, "StatID": 4, "Val": 70 }, { "Max": 100, "StaffID": 306, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 306, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 306, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 306, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 306, "StatID": 9, "Val": 58 }, { "Max": 100, "StaffID": 306, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 308, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 308, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 308, "StatID": 4, "Val": 59 }, { "Max": 100, "StaffID": 308, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 308, "StatID": 6, "Val": 57 }, { "Max": 100, "StaffID": 308, "StatID": 7, "Val": 58 }, { "Max": 100, "StaffID": 308, "StatID": 8, "Val": 64 }, { "Max": 100, "StaffID": 308, "StatID": 9, "Val": 56 }, { "Max": 100, "StaffID": 308, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 322, "StatID": 2, "Val": 69 }, { "Max": 100, "StaffID": 322, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 322, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 322, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 322, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 322, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 322, "StatID": 8, "Val": 64 }, { "Max": 100, "StaffID": 322, "StatID": 9, "Val": 72 }, { "Max": 100, "StaffID": 322, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 373, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 373, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 373, "StatID": 4, "Val": 60 }, { "Max": 100, "StaffID": 373, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 373, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 373, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 373, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 373, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 373, "StatID": 10, "Val": 70 }, { "Max": 100, "StaffID": 374, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 374, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 374, "StatID": 4, "Val": 65 }, { "Max": 100, "StaffID": 374, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 374, "StatID": 6, "Val": 69 }, { "Max": 100, "StaffID": 374, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 374, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 374, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 374, "StatID": 10, "Val": 66 }, { "Max": 100, "StaffID": 375, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 375, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 375, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 375, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 375, "StatID": 6, "Val": 72 }, { "Max": 100, "StaffID": 375, "StatID": 7, "Val": 68 }, { "Max": 100, "StaffID": 375, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 375, "StatID": 9, "Val": 64 }, { "Max": 100, "StaffID": 375, "StatID": 10, "Val": 65 }, { "Max": 100, "StaffID": 376, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 376, "StatID": 3, "Val": 85 }, { "Max": 100, "StaffID": 376, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 376, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 376, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 376, "StatID": 7, "Val": 81 }, { "Max": 100, "StaffID": 376, "StatID": 8, "Val": 80 }, { "Max": 100, "StaffID": 376, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 376, "StatID": 10, "Val": 80 }, { "Max": 100, "StaffID": 377, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 377, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 377, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 377, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 377, "StatID": 6, "Val": 70 }, { "Max": 100, "StaffID": 377, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 377, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 377, "StatID": 9, "Val": 73 }, { "Max": 100, "StaffID": 377, "StatID": 10, "Val": 72 }, { "Max": 100, "StaffID": 378, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 378, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 378, "StatID": 4, "Val": 69 }, { "Max": 100, "StaffID": 378, "StatID": 5, "Val": 71 }, { "Max": 100, "StaffID": 378, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 378, "StatID": 7, "Val": 77 }, { "Max": 100, "StaffID": 378, "StatID": 8, "Val": 78 }, { "Max": 100, "StaffID": 378, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 378, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 379, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 379, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 379, "StatID": 4, "Val": 67 }, { "Max": 100, "StaffID": 379, "StatID": 5, "Val": 58 }, { "Max": 100, "StaffID": 379, "StatID": 6, "Val": 66 }, { "Max": 100, "StaffID": 379, "StatID": 7, "Val": 68 }, { "Max": 100, "StaffID": 379, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 379, "StatID": 9, "Val": 65 }, { "Max": 100, "StaffID": 379, "StatID": 10, "Val": 67 }, { "Max": 100, "StaffID": 380, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 380, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 380, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 380, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 380, "StatID": 6, "Val": 67 }, { "Max": 100, "StaffID": 380, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 380, "StatID": 8, "Val": 64 }, { "Max": 100, "StaffID": 380, "StatID": 9, "Val": 60 }, { "Max": 100, "StaffID": 380, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 381, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 381, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 381, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 381, "StatID": 5, "Val": 57 }, { "Max": 100, "StaffID": 381, "StatID": 6, "Val": 66 }, { "Max": 100, "StaffID": 381, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 381, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 381, "StatID": 9, "Val": 72 }, { "Max": 100, "StaffID": 381, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 382, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 382, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 382, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 382, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 382, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 382, "StatID": 7, "Val": 57 }, { "Max": 100, "StaffID": 382, "StatID": 8, "Val": 65 }, { "Max": 100, "StaffID": 382, "StatID": 9, "Val": 60 }, { "Max": 100, "StaffID": 382, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 383, "StatID": 2, "Val": 57 }, { "Max": 100, "StaffID": 383, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 383, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 383, "StatID": 5, "Val": 59 }, { "Max": 100, "StaffID": 383, "StatID": 6, "Val": 67 }, { "Max": 100, "StaffID": 383, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 383, "StatID": 8, "Val": 65 }, { "Max": 100, "StaffID": 383, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 383, "StatID": 10, "Val": 66 }, { "Max": 100, "StaffID": 384, "StatID": 2, "Val": 62 }, { "Max": 100, "StaffID": 384, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 384, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 384, "StatID": 5, "Val": 58 }, { "Max": 100, "StaffID": 384, "StatID": 6, "Val": 67 }, { "Max": 100, "StaffID": 384, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 384, "StatID": 8, "Val": 63 }, { "Max": 100, "StaffID": 384, "StatID": 9, "Val": 63 }, { "Max": 100, "StaffID": 384, "StatID": 10, "Val": 64 }, { "Max": 100, "StaffID": 385, "StatID": 2, "Val": 55 }, { "Max": 100, "StaffID": 385, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 385, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 5, "Val": 48 }, { "Max": 100, "StaffID": 385, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 385, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 8, "Val": 60 }, { "Max": 100, "StaffID": 385, "StatID": 9, "Val": 62 }, { "Max": 100, "StaffID": 385, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 386, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 386, "StatID": 3, "Val": 58 }, { "Max": 100, "StaffID": 386, "StatID": 4, "Val": 60 }, { "Max": 100, "StaffID": 386, "StatID": 5, "Val": 66 }, { "Max": 100, "StaffID": 386, "StatID": 6, "Val": 66 }, { "Max": 100, "StaffID": 386, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 386, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 386, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 386, "StatID": 10, "Val": 59 }, { "Max": 100, "StaffID": 387, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 387, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 387, "StatID": 4, "Val": 66 }, { "Max": 100, "StaffID": 387, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 387, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 387, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 387, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 387, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 387, "StatID": 10, "Val": 64 }, { "Max": 100, "StaffID": 388, "StatID": 2, "Val": 57 }, { "Max": 100, "StaffID": 388, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 388, "StatID": 4, "Val": 69 }, { "Max": 100, "StaffID": 388, "StatID": 5, "Val": 59 }, { "Max": 100, "StaffID": 388, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 388, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 388, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 388, "StatID": 9, "Val": 63 }, { "Max": 100, "StaffID": 388, "StatID": 10, "Val": 57 }, { "Max": 100, "StaffID": 390, "StatID": 2, "Val": 61 }, { "Max": 100, "StaffID": 390, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 390, "StatID": 4, "Val": 57 }, { "Max": 100, "StaffID": 390, "StatID": 5, "Val": 56 }, { "Max": 100, "StaffID": 390, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 390, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 390, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 390, "StatID": 9, "Val": 59 }, { "Max": 100, "StaffID": 390, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 394, "StatID": 2, "Val": 62 }, { "Max": 100, "StaffID": 394, "StatID": 3, "Val": 58 }, { "Max": 100, "StaffID": 394, "StatID": 4, "Val": 59 }, { "Max": 100, "StaffID": 394, "StatID": 5, "Val": 54 }, { "Max": 100, "StaffID": 394, "StatID": 6, "Val": 61 }, { "Max": 100, "StaffID": 394, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 394, "StatID": 8, "Val": 60 }, { "Max": 100, "StaffID": 394, "StatID": 9, "Val": 58 }, { "Max": 100, "StaffID": 394, "StatID": 10, "Val": 64 }, { "Max": 100, "StaffID": 398, "StatID": 2, "Val": 69 }, { "Max": 100, "StaffID": 398, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 398, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 398, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 398, "StatID": 6, "Val": 80 }, { "Max": 100, "StaffID": 398, "StatID": 7, "Val": 80 }, { "Max": 100, "StaffID": 398, "StatID": 8, "Val": 71 }, { "Max": 100, "StaffID": 398, "StatID": 9, "Val": 77 }, { "Max": 100, "StaffID": 398, "StatID": 10, "Val": 74 }, { "Max": 100, "StaffID": 399, "StatID": 2, "Val": 70 }, { "Max": 100, "StaffID": 399, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 399, "StatID": 4, "Val": 72 }, { "Max": 100, "StaffID": 399, "StatID": 5, "Val": 55 }, { "Max": 100, "StaffID": 399, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 399, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 399, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 399, "StatID": 9, "Val": 60 }, { "Max": 100, "StaffID": 399, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 411, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 411, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 411, "StatID": 4, "Val": 66 }, { "Max": 100, "StaffID": 411, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 411, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 411, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 411, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 411, "StatID": 9, "Val": 69 }, { "Max": 100, "StaffID": 411, "StatID": 10, "Val": 58 }, { "Max": 100, "StaffID": 413, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 413, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 413, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 413, "StatID": 5, "Val": 72 }, { "Max": 100, "StaffID": 413, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 413, "StatID": 7, "Val": 76 }, { "Max": 100, "StaffID": 413, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 413, "StatID": 9, "Val": 79 }, { "Max": 100, "StaffID": 413, "StatID": 10, "Val": 78 } ], "Calendar": [ { "TrackID": 1, "Day": 45732, "WeekendType": 0 }, { "TrackID": 3, "Day": 45739, "WeekendType": 1 }, { "TrackID": 17, "Day": 45753, "WeekendType": 0 }, { "TrackID": 2, "Day": 45760, "WeekendType": 0 }, { "TrackID": 11, "Day": 45767, "WeekendType": 0 }, { "TrackID": 22, "Day": 45781, "WeekendType": 1 }, { "TrackID": 24, "Day": 45795, "WeekendType": 0 }, { "TrackID": 6, "Day": 45802, "WeekendType": 0 }, { "TrackID": 5, "Day": 45809, "WeekendType": 0 }, { "TrackID": 7, "Day": 45823, "WeekendType": 0 }, { "TrackID": 9, "Day": 45837, "WeekendType": 0 }, { "TrackID": 10, "Day": 45844, "WeekendType": 0 }, { "TrackID": 13, "Day": 45865, "WeekendType": 1 }, { "TrackID": 12, "Day": 45872, "WeekendType": 0 }, { "TrackID": 23, "Day": 45900, "WeekendType": 0 }, { "TrackID": 14, "Day": 45907, "WeekendType": 0 }, { "TrackID": 4, "Day": 45921, "WeekendType": 0 }, { "TrackID": 15, "Day": 45935, "WeekendType": 0 }, { "TrackID": 19, "Day": 45949, "WeekendType": 1 }, { "TrackID": 18, "Day": 45956, "WeekendType": 0 }, { "TrackID": 20, "Day": 45970, "WeekendType": 1 }, { "TrackID": 25, "Day": 45983, "WeekendType": 0 }, { "TrackID": 26, "Day": 45991, "WeekendType": 1 }, { "TrackID": 21, "Day": 45998, "WeekendType": 0 } ], "DriverStandings": [ { "DriverID": 1, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 223, "Position": 7, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 2, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 356, "Position": 3, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 3, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 12, "Position": 16, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 8, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 19, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 10, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 437, "Position": 1, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 11, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 290, "Position": 5, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 12, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 374, "Position": 2, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 13, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 12, "Position": 17, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 14, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 23, "Position": 14, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 15, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 42, "Position": 10, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 17, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 152, "Position": 8, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 18, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 24, "Position": 13, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 23, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 245, "Position": 6, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 77, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 70, "Position": 9, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 81, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 30, "Position": 12, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 83, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 41, "Position": 11, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 102, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 292, "Position": 4, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 105, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 4, "Position": 18, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 116, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 20, "RaceFormula": 1, "SeasonID": 2024 }, { "DriverID": 255, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 16, "Position": 15, "RaceFormula": 1, "SeasonID": 2024 } ], "TeamStandings": [ { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 652, "Position": 2, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 1 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 666, "Position": 1, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 2 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 589, "Position": 3, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 3 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 468, "Position": 4, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 4 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 65, "Position": 6, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 5 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 17, "Position": 9, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 6 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 58, "Position": 7, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 7 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 46, "Position": 8, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 8 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 4, "Position": 10, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 9 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 94, "Position": 5, "RaceFormula": 1, "SeasonID": 2024, "TeamID": 10 } ], "Staff_BasicData": [ { "StaffID": 600, "FirstName": "[STRING_LITERAL:Value=|Ella|]", "LastName": "[STRING_LITERAL:Value=|Lloyd|]", "CountryID": 186, "DOB": 38553, "DOB_ISO": "2005-07-20", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 0, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 601, "FirstName": "[STRING_LITERAL:Value=|John|]", "LastName": "[STRING_LITERAL:Value=|Bennett|]", "CountryID": 186, "DOB": 37879, "DOB_ISO": "2003-09-15", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 0, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 602, "FirstName": "[STRING_LITERAL:Value=|Brando|]", "LastName": "[STRING_LITERAL:Value=|Badoer|]", "CountryID": 79, "DOB": 38975, "DOB_ISO": "2006-09-15", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 1, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 603, "FirstName": "[STRING_LITERAL:Value=|Antonio|]", "LastName": "[STRING_LITERAL:Value=|Fuoco|]", "CountryID": 79, "DOB": 35205, "DOB_ISO": "1996-05-20", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 2, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 604, "FirstName": "[STRING_LITERAL:Value=|Rafael|]", "LastName": "[STRING_LITERAL:Value=|Câmara|]", "CountryID": 23, "DOB": 38477, "DOB_ISO": "2005-05-05", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 3, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 605, "FirstName": "[STRING_LITERAL:Value=|Doriane|]", "LastName": "[STRING_LITERAL:Value=|Pin|]", "CountryID": 57, "DOB": 37992, "DOB_ISO": "2004-01-06", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 1, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 606, "FirstName": "[STRING_LITERAL:Value=|Nina|]", "LastName": "[STRING_LITERAL:Value=|Gademan|]", "CountryID": 120, "DOB": 37894, "DOB_ISO": "2003-09-30", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 2, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 607, "FirstName": "[STRING_LITERAL:Value=|Courtney|]", "LastName": "[STRING_LITERAL:Value=|Crone|]", "CountryID": 187, "DOB": 36957, "DOB_ISO": "2001-03-07", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 3, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 608, "FirstName": "[STRING_LITERAL:Value=|Jamie|]", "LastName": "[STRING_LITERAL:Value=|Chadwick|]", "CountryID": 186, "DOB": 35935, "DOB_ISO": "1998-05-20", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 4, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 609, "FirstName": "[STRING_LITERAL:Value=|Lia|]", "LastName": "[STRING_LITERAL:Value=|Block|]", "CountryID": 187, "DOB": 38991, "DOB_ISO": "2006-10-01", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 5, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 610, "FirstName": "[STRING_LITERAL:Value=|Noah|]", "LastName": "[STRING_LITERAL:Value=|Strømsted|]", "CountryID": 43, "DOB": 39292, "DOB_ISO": "2007-07-29", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 4, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 611, "FirstName": "[STRING_LITERAL:Value=|James|]", "LastName": "[STRING_LITERAL:Value=|Wharton|]", "CountryID": 9, "DOB": 38906, "DOB_ISO": "2006-07-08", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 5, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 612, "FirstName": "[STRING_LITERAL:Value=|Gerrard|]", "LastName": "[STRING_LITERAL:Value=|Xie|]", "CountryID": 35, "DOB": 38984, "DOB_ISO": "2006-09-24", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 4, "FaceIndex": 0, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 613, "FirstName": "[STRING_LITERAL:Value=|Bruno|]", "LastName": "[STRING_LITERAL:Value=|del Pino|]", "CountryID": 160, "DOB": 38888, "DOB_ISO": "2006-06-20", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 6, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 614, "FirstName": "[STRING_LITERAL:Value=|Alessandro|]", "LastName": "[STRING_LITERAL:Value=|Giusti|]", "CountryID": 57, "DOB": 38970, "DOB_ISO": "2006-09-10", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 7, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 615, "FirstName": "[STRING_LITERAL:Value=|Théophile|]", "LastName": "[STRING_LITERAL:Value=|Naël|]", "CountryID": 57, "DOB": 39255, "DOB_ISO": "2007-06-22", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 8, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 616, "FirstName": "[STRING_LITERAL:Value=|Ivan|]", "LastName": "[STRING_LITERAL:Value=|Domingues|]", "CountryID": 136, "DOB": 38842, "DOB_ISO": "2006-05-05", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 9, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 617, "FirstName": "[STRING_LITERAL:Value=|Louis|]", "LastName": "[STRING_LITERAL:Value=|Sharp|]", "CountryID": 121, "DOB": 39213, "DOB_ISO": "2007-05-11", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 10, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 618, "FirstName": "[STRING_LITERAL:Value=|Roman|]", "LastName": "[STRING_LITERAL:Value=|Bilinski|]", "CountryID": 135, "DOB": 38050, "DOB_ISO": "2004-03-04", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 619, "FirstName": "[STRING_LITERAL:Value=|Javier|]", "LastName": "[STRING_LITERAL:Value=|Sagrera|]", "CountryID": 160, "DOB": 37998, "DOB_ISO": "2004-01-12", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 12, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 620, "FirstName": "[STRING_LITERAL:Value=|Nicola|]", "LastName": "[STRING_LITERAL:Value=|Marinangeli|]", "CountryID": 79, "DOB": 37755, "DOB_ISO": "2003-05-14", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 13, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 621, "FirstName": "[STRING_LITERAL:Value=|Christian|]", "LastName": "[STRING_LITERAL:Value=|Ho|]", "CountryID": 152, "DOB": 39021, "DOB_ISO": "2006-10-31", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 4, "FaceIndex": 1, "AgeType": 0, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 622, "FirstName": "[STRING_LITERAL:Value=|Bryan|]", "LastName": "[STRING_LITERAL:Value=|Bozzi|]", "CountryID": 79, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 0, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 623, "FirstName": "[STRING_LITERAL:Value=|Richard|]", "LastName": "[STRING_LITERAL:Value=|Wood|]", "CountryID": 186, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 624, "FirstName": "[STRING_LITERAL:Value=|Laura|]", "LastName": "[STRING_LITERAL:Value=|Mueller|]", "CountryID": 60, "DOB": 32874, "DOB_ISO": "1990-01-01", "Gender": 1, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 0, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 625, "FirstName": "[STRING_LITERAL:Value=|Ronan|]", "LastName": "[STRING_LITERAL:Value=|O'Hare|]", "CountryID": 186, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 626, "FirstName": "[STRING_LITERAL:Value=|Ernesto|]", "LastName": "[STRING_LITERAL:Value=|Desiderio|]", "CountryID": 79, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 1, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 628, "FirstName": "[STRING_LITERAL:Value=|Rob|]", "LastName": "[STRING_LITERAL:Value=|Marshall|]", "CountryID": 186, "DOB": 24942, "DOB_ISO": "1968-04-14", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 2, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 630, "FirstName": "[STRING_LITERAL:Value=|David|]", "LastName": "[STRING_LITERAL:Value=|Sanchez|]", "CountryID": 57, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 3, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 633, "FirstName": "[STRING_LITERAL:Value=|Selin|]", "LastName": "[STRING_LITERAL:Value=|Tur|]", "CountryID": 186, "DOB": 28518, "DOB_ISO": "1978-01-28", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 1, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 635, "FirstName": "[STRING_LITERAL:Value=|Sean|]", "LastName": "[STRING_LITERAL:Value=|Whitehead|]", "CountryID": 186, "DOB": 23025, "DOB_ISO": "1963-01-14", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 636, "FirstName": "[STRING_LITERAL:Value=|Mark|]", "LastName": "[STRING_LITERAL:Value=|Robinson|]", "CountryID": 186, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 637, "FirstName": "[STRING_LITERAL:Value=|Peter|]", "LastName": "[STRING_LITERAL:Value=|Machin|]", "CountryID": 186, "DOB": 24838, "DOB_ISO": "1968-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 11, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 638, "FirstName": "[STRING_LITERAL:Value=|Will|]", "LastName": "[STRING_LITERAL:Value=|Courtenay|]", "CountryID": 186, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 4, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 639, "FirstName": "[STRING_LITERAL:Value=|Richard|]", "LastName": "[STRING_LITERAL:Value=|Wolverson|]", "CountryID": 186, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 5, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 640, "FirstName": "[STRING_LITERAL:Value=|Mark|]", "LastName": "[STRING_LITERAL:Value=|Lowe|]", "CountryID": 187, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 6, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 641, "FirstName": "[STRING_LITERAL:Value=|Ignacio|]", "LastName": "[STRING_LITERAL:Value=|Rueda|]", "CountryID": 160, "DOB": 28708, "DOB_ISO": "1978-08-06", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 7, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 642, "FirstName": "[STRING_LITERAL:Value=|Loïc|]", "LastName": "[STRING_LITERAL:Value=|Serra|]", "CountryID": 57, "DOB": 26388, "DOB_ISO": "1972-03-30", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 8, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 643, "FirstName": "[STRING_LITERAL:Value=|Steven|]", "LastName": "[STRING_LITERAL:Value=|Petrik|]", "CountryID": 187, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 9, "AgeType": 1, "IsGeneratedForCustomTeam": 0 }, { "StaffID": 644, "FirstName": "[STRING_LITERAL:Value=|Jose Manuel|]", "LastName": "[STRING_LITERAL:Value=|Lopez|]", "CountryID": 160, "DOB": 29221, "DOB_ISO": "1980-01-01", "Gender": 0, "IsGeneratedStaff": 1, "PhotoDay": 45340, "FaceType": 0, "FaceIndex": 10, "AgeType": 1, "IsGeneratedForCustomTeam": 0 } ], "Staff_PerformanceStats": [ { "StaffID": 600, "StatID": 2, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 3, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 4, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 5, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 6, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 7, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 8, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 9, "Val": 57, "Max": 100 }, { "StaffID": 600, "StatID": 10, "Val": 57, "Max": 100 }, { "StaffID": 601, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 601, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 602, "StatID": 2, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 3, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 5, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 6, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 7, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 8, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 603, "StatID": 2, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 3, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 4, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 5, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 6, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 7, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 8, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 9, "Val": 75, "Max": 100 }, { "StaffID": 603, "StatID": 10, "Val": 75, "Max": 100 }, { "StaffID": 604, "StatID": 2, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 3, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 5, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 6, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 7, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 8, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 604, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 605, "StatID": 2, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 3, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 4, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 5, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 6, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 7, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 8, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 9, "Val": 61, "Max": 100 }, { "StaffID": 605, "StatID": 10, "Val": 61, "Max": 100 }, { "StaffID": 606, "StatID": 2, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 3, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 4, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 5, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 6, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 7, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 8, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 10, "Val": 56, "Max": 100 }, { "StaffID": 607, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 3, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 4, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 5, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 6, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 7, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 8, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 607, "StatID": 10, "Val": 58, "Max": 100 }, { "StaffID": 608, "StatID": 2, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 3, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 4, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 5, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 6, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 7, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 8, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 9, "Val": 72, "Max": 100 }, { "StaffID": 608, "StatID": 10, "Val": 72, "Max": 100 }, { "StaffID": 609, "StatID": 2, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 3, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 4, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 5, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 6, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 7, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 8, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 609, "StatID": 10, "Val": 56, "Max": 100 }, { "StaffID": 610, "StatID": 2, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 3, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 5, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 6, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 7, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 8, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 2, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 3, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 5, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 6, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 7, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 8, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 612, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 3, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 4, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 5, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 6, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 7, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 8, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 612, "StatID": 10, "Val": 58, "Max": 100 }, { "StaffID": 613, "StatID": 2, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 3, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 4, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 5, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 6, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 7, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 8, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 9, "Val": 61, "Max": 100 }, { "StaffID": 613, "StatID": 10, "Val": 61, "Max": 100 }, { "StaffID": 614, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 614, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 615, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 616, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 617, "StatID": 2, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 3, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 4, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 5, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 6, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 7, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 8, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 9, "Val": 59, "Max": 100 }, { "StaffID": 617, "StatID": 10, "Val": 59, "Max": 100 }, { "StaffID": 618, "StatID": 2, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 3, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 4, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 5, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 6, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 7, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 8, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 9, "Val": 63, "Max": 100 }, { "StaffID": 618, "StatID": 10, "Val": 63, "Max": 100 }, { "StaffID": 619, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 619, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 2, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 3, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 4, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 5, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 6, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 7, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 9, "Val": 60, "Max": 100 }, { "StaffID": 620, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 621, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 3, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 4, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 5, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 6, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 7, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 8, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 621, "StatID": 10, "Val": 58, "Max": 100 }, { "StaffID": 622, "StatID": 13, "Val": 86, "Max": 100 }, { "StaffID": 622, "StatID": 25, "Val": 86, "Max": 100 }, { "StaffID": 622, "StatID": 43, "Val": 84, "Max": 100 }, { "StaffID": 623, "StatID": 13, "Val": 87, "Max": 100 }, { "StaffID": 623, "StatID": 25, "Val": 86, "Max": 100 }, { "StaffID": 623, "StatID": 43, "Val": 88, "Max": 100 }, { "StaffID": 624, "StatID": 13, "Val": 78, "Max": 100 }, { "StaffID": 624, "StatID": 25, "Val": 80, "Max": 100 }, { "StaffID": 624, "StatID": 43, "Val": 76, "Max": 100 }, { "StaffID": 625, "StatID": 13, "Val": 84, "Max": 100 }, { "StaffID": 625, "StatID": 25, "Val": 81, "Max": 100 }, { "StaffID": 625, "StatID": 43, "Val": 82, "Max": 100 }, { "StaffID": 626, "StatID": 13, "Val": 81, "Max": 100 }, { "StaffID": 626, "StatID": 25, "Val": 78, "Max": 100 }, { "StaffID": 626, "StatID": 43, "Val": 80, "Max": 100 }, { "StaffID": 628, "StatID": 0, "Val": 94, "Max": 100 }, { "StaffID": 628, "StatID": 1, "Val": 94, "Max": 100 }, { "StaffID": 628, "StatID": 14, "Val": 91, "Max": 100 }, { "StaffID": 628, "StatID": 15, "Val": 90, "Max": 100 }, { "StaffID": 628, "StatID": 16, "Val": 90, "Max": 100 }, { "StaffID": 628, "StatID": 17, "Val": 91, "Max": 100 }, { "StaffID": 630, "StatID": 0, "Val": 89, "Max": 100 }, { "StaffID": 630, "StatID": 1, "Val": 87, "Max": 100 }, { "StaffID": 630, "StatID": 14, "Val": 87, "Max": 100 }, { "StaffID": 630, "StatID": 15, "Val": 85, "Max": 100 }, { "StaffID": 630, "StatID": 16, "Val": 86, "Max": 100 }, { "StaffID": 630, "StatID": 17, "Val": 88, "Max": 100 }, { "StaffID": 633, "StatID": 0, "Val": 82, "Max": 100 }, { "StaffID": 633, "StatID": 1, "Val": 82, "Max": 100 }, { "StaffID": 633, "StatID": 14, "Val": 85, "Max": 100 }, { "StaffID": 633, "StatID": 15, "Val": 81, "Max": 100 }, { "StaffID": 633, "StatID": 16, "Val": 81, "Max": 100 }, { "StaffID": 633, "StatID": 17, "Val": 83, "Max": 100 }, { "StaffID": 635, "StatID": 19, "Val": 89, "Max": 100 }, { "StaffID": 635, "StatID": 20, "Val": 89, "Max": 100 }, { "StaffID": 635, "StatID": 26, "Val": 89, "Max": 100 }, { "StaffID": 635, "StatID": 27, "Val": 92, "Max": 100 }, { "StaffID": 635, "StatID": 28, "Val": 90, "Max": 100 }, { "StaffID": 635, "StatID": 29, "Val": 90, "Max": 100 }, { "StaffID": 635, "StatID": 30, "Val": 90, "Max": 100 }, { "StaffID": 635, "StatID": 31, "Val": 91, "Max": 100 }, { "StaffID": 636, "StatID": 19, "Val": 89, "Max": 100 }, { "StaffID": 636, "StatID": 20, "Val": 88, "Max": 100 }, { "StaffID": 636, "StatID": 26, "Val": 86, "Max": 100 }, { "StaffID": 636, "StatID": 27, "Val": 86, "Max": 100 }, { "StaffID": 636, "StatID": 28, "Val": 85, "Max": 100 }, { "StaffID": 636, "StatID": 29, "Val": 87, "Max": 100 }, { "StaffID": 636, "StatID": 30, "Val": 86, "Max": 100 }, { "StaffID": 636, "StatID": 31, "Val": 86, "Max": 100 }, { "StaffID": 637, "StatID": 19, "Val": 87, "Max": 100 }, { "StaffID": 637, "StatID": 20, "Val": 83, "Max": 100 }, { "StaffID": 637, "StatID": 26, "Val": 84, "Max": 100 }, { "StaffID": 637, "StatID": 27, "Val": 85, "Max": 100 }, { "StaffID": 637, "StatID": 28, "Val": 83, "Max": 100 }, { "StaffID": 637, "StatID": 29, "Val": 83, "Max": 100 }, { "StaffID": 637, "StatID": 30, "Val": 84, "Max": 100 }, { "StaffID": 637, "StatID": 31, "Val": 86, "Max": 100 }, { "StaffID": 638, "StatID": 11, "Val": 88, "Max": 100 }, { "StaffID": 638, "StatID": 22, "Val": 88, "Max": 100 }, { "StaffID": 638, "StatID": 23, "Val": 86, "Max": 100 }, { "StaffID": 638, "StatID": 24, "Val": 88, "Max": 100 }, { "StaffID": 639, "StatID": 11, "Val": 91, "Max": 100 }, { "StaffID": 639, "StatID": 22, "Val": 87, "Max": 100 }, { "StaffID": 639, "StatID": 23, "Val": 87, "Max": 100 }, { "StaffID": 639, "StatID": 24, "Val": 89, "Max": 100 }, { "StaffID": 640, "StatID": 11, "Val": 81, "Max": 100 }, { "StaffID": 640, "StatID": 22, "Val": 80, "Max": 100 }, { "StaffID": 640, "StatID": 23, "Val": 80, "Max": 100 }, { "StaffID": 640, "StatID": 24, "Val": 79, "Max": 100 }, { "StaffID": 641, "StatID": 11, "Val": 88, "Max": 100 }, { "StaffID": 641, "StatID": 22, "Val": 87, "Max": 100 }, { "StaffID": 641, "StatID": 23, "Val": 86, "Max": 100 }, { "StaffID": 641, "StatID": 24, "Val": 87, "Max": 100 }, { "StaffID": 642, "StatID": 0, "Val": 81, "Max": 100 }, { "StaffID": 642, "StatID": 1, "Val": 83, "Max": 100 }, { "StaffID": 642, "StatID": 14, "Val": 81, "Max": 100 }, { "StaffID": 642, "StatID": 15, "Val": 84, "Max": 100 }, { "StaffID": 642, "StatID": 16, "Val": 82, "Max": 100 }, { "StaffID": 642, "StatID": 17, "Val": 84, "Max": 100 }, { "StaffID": 643, "StatID": 13, "Val": 85, "Max": 100 }, { "StaffID": 643, "StatID": 25, "Val": 81, "Max": 100 }, { "StaffID": 643, "StatID": 43, "Val": 80, "Max": 100 }, { "StaffID": 644, "StatID": 13, "Val": 79, "Max": 100 }, { "StaffID": 644, "StatID": 25, "Val": 78, "Max": 100 }, { "StaffID": 644, "StatID": 43, "Val": 84, "Max": 100 } ], "Staff_State": [ { "StaffID": 600, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 601, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 602, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 603, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 604, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 605, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 606, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 607, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 608, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 609, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 610, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 611, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 612, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 613, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 614, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 615, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 616, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 617, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 618, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 619, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 620, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 621, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 622, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 623, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 624, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 625, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 626, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 628, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 630, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 633, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 635, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 636, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 637, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 638, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 639, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 640, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 641, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 }, { "StaffID": 642, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0, "Mentality": 80, "MentalityOpinion": 2 } ], "Staff_DriverData": [ { "StaffID": 600, "Improvability": 80, "Aggression": 63, "DriverCode": "[STRING_LITERAL:Value=|LLO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 80, "TargetMarketability": 80, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 601, "Improvability": 72, "Aggression": 41, "DriverCode": "[STRING_LITERAL:Value=|BEN|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 67, "TargetMarketability": 67, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 602, "Improvability": 75, "Aggression": 57, "DriverCode": "[STRING_LITERAL:Value=|BAD|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 40, "TargetMarketability": 40, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 603, "Improvability": 68, "Aggression": 51, "DriverCode": "[STRING_LITERAL:Value=|FUO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 70, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 604, "Improvability": 87, "Aggression": 68, "DriverCode": "[STRING_LITERAL:Value=|CAM|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 72, "TargetMarketability": 72, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 605, "Improvability": 67, "Aggression": 73, "DriverCode": "[STRING_LITERAL:Value=|PIN|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 54, "TargetMarketability": 54, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 606, "Improvability": 84, "Aggression": 76, "DriverCode": "[STRING_LITERAL:Value=|GAD|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 69, "TargetMarketability": 69, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 607, "Improvability": 74, "Aggression": 57, "DriverCode": "[STRING_LITERAL:Value=|CRO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 44, "TargetMarketability": 44, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 608, "Improvability": 65, "Aggression": 52, "DriverCode": "[STRING_LITERAL:Value=|CHA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 65, "TargetMarketability": 65, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 609, "Improvability": 71, "Aggression": 54, "DriverCode": "[STRING_LITERAL:Value=|BLO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 73, "TargetMarketability": 73, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 610, "Improvability": 71, "Aggression": 55, "DriverCode": "[STRING_LITERAL:Value=|STR|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 49, "TargetMarketability": 49, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 611, "Improvability": 77, "Aggression": 76, "DriverCode": "[STRING_LITERAL:Value=|WHA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 50, "TargetMarketability": 50, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 612, "Improvability": 75, "Aggression": 40, "DriverCode": "[STRING_LITERAL:Value=|XIE|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 52, "TargetMarketability": 52, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 613, "Improvability": 79, "Aggression": 69, "DriverCode": "[STRING_LITERAL:Value=|DEL|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 76, "TargetMarketability": 76, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 614, "Improvability": 79, "Aggression": 69, "DriverCode": "[STRING_LITERAL:Value=|GIU|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 42, "TargetMarketability": 42, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 615, "Improvability": 75, "Aggression": 56, "DriverCode": "[STRING_LITERAL:Value=|NAE|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 49, "TargetMarketability": 49, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 616, "Improvability": 70, "Aggression": 43, "DriverCode": "[STRING_LITERAL:Value=|DOM|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 42, "TargetMarketability": 42, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 617, "Improvability": 70, "Aggression": 51, "DriverCode": "[STRING_LITERAL:Value=|SHA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 48, "TargetMarketability": 48, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 618, "Improvability": 72, "Aggression": 41, "DriverCode": "[STRING_LITERAL:Value=|BIL|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 54, "TargetMarketability": 54, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 619, "Improvability": 75, "Aggression": 45, "DriverCode": "[STRING_LITERAL:Value=|SAG|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 66, "TargetMarketability": 66, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 620, "Improvability": 75, "Aggression": 58, "DriverCode": "[STRING_LITERAL:Value=|MAR|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 63, "TargetMarketability": 63, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 621, "Improvability": 73, "Aggression": 42, "DriverCode": "[STRING_LITERAL:Value=|HO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 57, "TargetMarketability": 57, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null } ], "Staff_GameData": [ { "StaffID": 600, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 601, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 602, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 603, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": "2", "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 604, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 605, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 606, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 607, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 608, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 609, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 610, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 611, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 612, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 613, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 614, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 615, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 616, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 617, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 618, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 619, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 620, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 621, "StaffType": 0, "RetirementAge": 35, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 622, "StaffType": 2, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 623, "StaffType": 2, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 624, "StaffType": 2, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 625, "StaffType": 2, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 626, "StaffType": 2, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 628, "StaffType": 1, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 630, "StaffType": 1, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 633, "StaffType": 1, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 635, "StaffType": 3, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 636, "StaffType": 3, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 637, "StaffType": 3, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 638, "StaffType": 4, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 639, "StaffType": 4, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 640, "StaffType": 4, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 641, "StaffType": 4, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 642, "StaffType": 1, "RetirementAge": 70, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 643, "StaffType": 2, "RetirementAge": 63, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 }, { "StaffID": 644, "StaffType": 2, "RetirementAge": 61, "Retired": 0, "PermaTraitSpawnBoost": 0, "BestTeamFormula": null, "BestF1PosInTeamSinceGameStart": null, "DevelopmentPlan": null, "ExpectedRankForTeam": 5, "AchievementScore": 0, "ExpectedQualityScore": 0, "ExpectedTimeScore": 0 } ], "TeamLineUps": [ { "TeamID": 1, "Driver1": 2, "Driver2": 1, "Engineer1": 622, "Engineer2": 7, "TechnicalChief": 642, "HeadAero": 26, "SportingDirector": 392 }, { "TeamID": 2, "Driver1": 12, "Driver2": 102, "Engineer1": 48, "Engineer2": 47, "TechnicalChief": 628, "HeadAero": 337, "SportingDirector": 638 }, { "TeamID": 3, "Driver1": 10, "Driver2": 95, "Engineer1": 50, "Engineer2": 623, "TechnicalChief": 28, "HeadAero": 635, "SportingDirector": 639 }, { "TeamID": 4, "Driver1": 23, "Driver2": 376, "Engineer1": 320, "Engineer2": 51, "TechnicalChief": 333, "HeadAero": 38, "SportingDirector": 292 }, { "TeamID": 5, "Driver1": 15, "Driver2": 135, "Engineer1": 415, "Engineer2": 54, "TechnicalChief": 630, "HeadAero": 40, "SportingDirector": 397 }, { "TeamID": 6, "Driver1": 11, "Driver2": 3, "Engineer1": 56, "Engineer2": 55, "TechnicalChief": 633, "HeadAero": 369, "SportingDirector": 370 }, { "TeamID": 7, "Driver1": 14, "Driver2": 142, "Engineer1": 624, "Engineer2": 625, "TechnicalChief": 403, "HeadAero": 404, "SportingDirector": 640 }, { "TeamID": 8, "Driver1": 144, "Driver2": 81, "Engineer1": 59, "Engineer2": 626, "TechnicalChief": 33, "HeadAero": 637, "SportingDirector": 295 }, { "TeamID": 9, "Driver1": 279, "Driver2": 83, "Engineer1": 644, "Engineer2": 643, "TechnicalChief": 334, "HeadAero": 43, "SportingDirector": 641 }, { "TeamID": 10, "Driver1": 77, "Driver2": 18, "Engineer1": 63, "Engineer2": 58, "TechnicalChief": 4, "HeadAero": 636, "SportingDirector": 367 } ], "Performance": [ { "TeamID": 1, "Boost": 1.100, "TyreDeg": 4 }, { "TeamID": 2, "Boost": 1.139, "TyreDeg": 1 }, { "TeamID": 3, "Boost": 1.011, "TyreDeg": 2 }, { "TeamID": 4, "Boost": 1.207, "TyreDeg": 10 }, { "TeamID": 5, "Boost": 1.320, "TyreDeg": 8 }, { "TeamID": 6, "Boost": 1.257, "TyreDeg": 6 }, { "TeamID": 7, "Boost": 1.161, "TyreDeg": 3 }, { "TeamID": 8, "Boost": 1.137, "TyreDeg": 9 }, { "TeamID": 9, "Boost": 1.277, "TyreDeg": 5 }, { "TeamID": 10, "Boost": 1.052, "TyreDeg": 5 }, { "TeamID": 32, "Boost": 1.166, "TyreDeg": 6 } ], "Fixes": [ { "StaffID": 600, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 600, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 0 }, { "StaffID": 600, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 601, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 601, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 0 }, { "StaffID": 601, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 602, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 602, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 1 }, { "StaffID": 602, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 603, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 603, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 2 }, { "StaffID": 603, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 604, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 604, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 3 }, { "StaffID": 604, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 605, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 605, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 1 }, { "StaffID": 605, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 606, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 606, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 2 }, { "StaffID": 606, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 607, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 607, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 3 }, { "StaffID": 607, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 608, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 608, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 4 }, { "StaffID": 608, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 609, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 609, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 5 }, { "StaffID": 609, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 610, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 610, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 4 }, { "StaffID": 610, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 611, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 611, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 5 }, { "StaffID": 611, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 612, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 4 }, { "StaffID": 612, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 0 }, { "StaffID": 612, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 613, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 613, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 6 }, { "StaffID": 613, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 614, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 614, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 7 }, { "StaffID": 614, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 615, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 615, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 8 }, { "StaffID": 615, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 616, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 616, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 9 }, { "StaffID": 616, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 617, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 617, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 10 }, { "StaffID": 617, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 618, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 618, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 618, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 619, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 619, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 12 }, { "StaffID": 619, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 620, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 620, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 13 }, { "StaffID": 620, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 621, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 4 }, { "StaffID": 621, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 1 }, { "StaffID": 621, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 0 }, { "StaffID": 622, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 622, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 0 }, { "StaffID": 622, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 623, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 623, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 623, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 624, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 624, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 0 }, { "StaffID": 624, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 624, "Table": "Staff_BasicData", "Column": "Gender", "Value": 1 }, { "StaffID": 625, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 625, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 625, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 626, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 626, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 1 }, { "StaffID": 626, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 628, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 628, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 2 }, { "StaffID": 628, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 630, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 630, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 3 }, { "StaffID": 630, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 633, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 633, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 1 }, { "StaffID": 633, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 633, "Table": "Staff_BasicData", "Column": "Gender", "Value": 1 }, { "StaffID": 635, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 635, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 635, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 636, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 636, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 636, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 637, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 637, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 11 }, { "StaffID": 637, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 638, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 638, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 4 }, { "StaffID": 638, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 639, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 639, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 5 }, { "StaffID": 639, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 640, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 640, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 6 }, { "StaffID": 640, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 641, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 641, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 7 }, { "StaffID": 641, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 642, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 642, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 8 }, { "StaffID": 642, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 643, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 643, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 9 }, { "StaffID": 643, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 }, { "StaffID": 644, "Table": "Staff_BasicData", "Column": "FaceType", "Value": 0 }, { "StaffID": 644, "Table": "Staff_BasicData", "Column": "FaceIndex", "Value": 10 }, { "StaffID": 644, "Table": "Staff_BasicData", "Column": "AgeType", "Value": 1 } ] } ================================================ FILE: src/data/2026_changes.json ================================================ { "Calendar": [ { "TrackID": 1, "Day": 46089, "WeekendType": 0 }, { "TrackID": 3, "Day": 46096, "WeekendType": 1 }, { "TrackID": 17, "Day": 46110, "WeekendType": 0 }, { "TrackID": 2, "Day": 46124, "WeekendType": 0, "deleteIfGameAdds": true }, { "TrackID": 11, "Day": 46131, "WeekendType": 0, "deleteIfGameAdds": true }, { "TrackID": 22, "Day": 46145, "WeekendType": 1, "isF2Weekend": true }, { "TrackID": 7, "Day": 46166, "WeekendType": 1, "isF2Weekend": true }, { "TrackID": 6, "Day": 46180, "WeekendType": 0 }, { "TrackID": 5, "Day": 46187, "WeekendType": 0 }, { "TrackID": 9, "Day": 46201, "WeekendType": 0 }, { "TrackID": 10, "Day": 46208, "WeekendType": 1 }, { "TrackID": 13, "Day": 46222, "WeekendType": 0 }, { "TrackID": 12, "Day": 46229, "WeekendType": 0 }, { "TrackID": 23, "Day": 46257, "WeekendType": 1, "isF2Weekend": false }, { "TrackID": 14, "Day": 46271, "WeekendType": 0 }, { "TrackID": null, "Day": 46278, "WeekendType": 0, "deleteIfGameAdds": true }, { "TrackID": 4, "Day": 46292, "WeekendType": 0 }, { "TrackID": 15, "Day": 46306, "WeekendType": 1 }, { "TrackID": 19, "Day": 46320, "WeekendType": 0 }, { "TrackID": 18, "Day": 46327, "WeekendType": 0 }, { "TrackID": 20, "Day": 46334, "WeekendType": 0 }, { "TrackID": 25, "Day": 46347, "WeekendType": 0 }, { "TrackID": 26, "Day": 46355, "WeekendType": 0 }, { "TrackID": 21, "Day": 46362, "WeekendType": 0 } ], "TeamStandings": [ { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 398, "Position": 4, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 1 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 833, "Position": 1, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 2 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 451, "Position": 3, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 3 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 469, "Position": 2, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 4 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 22, "Position": 10, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 5 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 137, "Position": 5, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 6 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 79, "Position": 8, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 7 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 92, "Position": 6, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 8 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 70, "Position": 9, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 9 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 89, "Position": 7, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 10 }, { "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 11, "RaceFormula": 1, "SeasonID": 2025, "TeamID": 32 } ], "DriverStandings": [ { "DriverID": 1, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 156, "Position": 6, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 2, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 242, "Position": 5, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 3, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 73, "Position": 8, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 8, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 23, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 10, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 421, "Position": 2, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 11, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 64, "Position": 9, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 12, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 423, "Position": 1, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 14, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 38, "Position": 15, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 15, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 22, "Position": 18, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 17, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 22, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 18, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 33, "Position": 16, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 23, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 319, "Position": 4, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 77, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 56, "Position": 10, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 81, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 33, "Position": 17, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 83, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 51, "Position": 11, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 102, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 410, "Position": 3, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 376, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 150, "Position": 7, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 144, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 51, "Position": 12, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 142, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 41, "Position": 13, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 95, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 38, "Position": 14, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 279, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 19, "Position": 19, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 248, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 20, "RaceFormula": 1, "SeasonID": 2025 }, { "DriverID": 135, "LastPointsChange": 0, "LastPositionChange": 0, "Points": 0, "Position": 21, "RaceFormula": 1, "SeasonID": 2025 } ], "EnginePerformance": { "mercedes": { "10": 92, "6": 80, "14": 60, "18": 63, "19": 80 }, "ferrari": { "10": 71, "6": 95, "14": 80, "18": 75, "19": 80 }, "rbpt": { "10": 85, "6": 73, "14": 79, "18": 75, "19": 85 }, "audi": { "10": 58, "6": 75, "14": 55, "18": 70, "19": 45 }, "honda": { "10": 19, "6": 70, "14": 10, "18": 10, "19": 10 } }, "Performance": [ { "TeamID": 1, "Boost": 0.60, "TyreDeg": 10 }, { "TeamID": 2, "Boost": 0.52, "TyreDeg": 1 }, { "TeamID": 3, "Boost": 0.41, "TyreDeg": 8 }, { "TeamID": 4, "Boost": 0.63, "TyreDeg": 1 }, { "TeamID": 5, "Boost": 0.59, "TyreDeg": 10 }, { "TeamID": 6, "Boost": 0.32, "TyreDeg": 10 }, { "TeamID": 7, "Boost": 0.55, "TyreDeg": 1 }, { "TeamID": 8, "Boost": 0.45, "TyreDeg": 10 }, { "TeamID": 9, "Boost": 0.56, "TyreDeg": 10 }, { "TeamID": 10, "Boost": 0.4, "TyreDeg": 4 }, { "TeamID": 32, "Boost": 0.30, "TyreDeg": 6, "Objective": 13.2 } ], "Regulations": [ { "Name": "SpendingCap", "CurrentValue": 215000000, "MinValue": 125000000, "MaxValue": 215000000 } ], "Facilities" : [ { "TeamID": 10, "UpgradeBy": 1 }, { "TeamID": 9, "UpgradeBy": 1 } ], "Records" : [ { "StaffID": 12, "Wins": 11, "Podiums": 31, "Poles": 15, "FastestLaps": 12, "Championships": 1 }, { "StaffID": 10, "Wins": 17, "Podiums": 29, "Poles": 16, "FastestLaps": 6, "Championships": 1 }, { "StaffID": 102, "Wins": 9, "Podiums": 24, "Poles": 6, "FastestLaps": 7, "Championships": 0 }, { "StaffID": 23, "Wins": 4, "Podiums": 13, "Poles": 6, "FastestLaps": 5, "Championships": 0 }, { "StaffID": 376, "Wins": 0, "Podiums": 3, "Poles": 0, "FastestLaps": 3, "Championships": 0 }, { "StaffID": 2, "Wins": 3, "Podiums": 20, "Poles": 4, "FastestLaps": 4, "Championships": 0 }, { "StaffID": 11, "Wins": 2, "Podiums": 11, "Poles": 1, "FastestLaps": 1, "Championships": 0 }, { "StaffID": 1, "Wins": 2, "Podiums": 5, "Poles": 0, "FastestLaps": 3, "Championships": 0 }, { "StaffID": 17, "Wins": 0, "Podiums": 4, "Poles": 0, "FastestLaps": 1, "Championships": 0 }, { "StaffID": 77, "Wins": 0, "Podiums": 0, "Poles": 0, "FastestLaps": 2, "Championships": 0 }, { "StaffID": 3, "Wins": 0, "Podiums": 0, "Poles": 0, "FastestLaps": 1, "Championships": 0 }, { "StaffID": 14, "Wins": 0, "Podiums": 1, "Poles": 0, "FastestLaps": 1, "Championships": 0 }, { "StaffID": 15, "Wins": 0, "Podiums": 1, "Poles": 0, "FastestLaps": 0, "Championships": 0 }, { "StaffID": 83, "Wins": 0, "Podiums": 1, "Poles": 0, "FastestLaps": 0, "Championships": 0 }, { "StaffID": 13, "Wins": 0, "Podiums": 0, "Poles": 0, "FastestLaps": 1, "Championships": 0 }, { "StaffID": 255, "Wins": 0, "Podiums": 0, "Poles": 0, "FastestLaps": 1, "Championships": 0 } ] } ================================================ FILE: src/data/contracts_2025.json ================================================ { "Updates": [ { "DriverID": 10, "salary": 52000000, "StartingBonus": 2000000, "RaceBonus": 1000000, "RaceBonusTargetPos": 1, "EndSeason": 2028, "BreakoutClause": 0.5 }, { "DriverID": 2, "salary": 27000000, "StartingBonus": 2000000, "RaceBonus": 400000, "RaceBonusTargetPos": 1, "EndSeason": 2028, "BreakoutClause": 0.5 }, { "DriverID": 23, "salary": 12000000, "StartingBonus": 1000000, "RaceBonus": 100000, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 12, "salary": 16000000, "StartingBonus": 1000000, "RaceBonus": 300000, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "DriverID": 102, "salary": 15000000, "StartingBonus": 3000000, "RaceBonus": 100000, "RaceBonusTargetPos": 1, "EndSeason": 2028, "BreakoutClause": 0.5 }, { "DriverID": 18, "salary": 2300000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 77, "salary": 16000000, "StartingBonus": 1000000, "RaceBonus": 400000, "RaceBonusTargetPos": 3, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 15, "salary": 8000000, "StartingBonus": 200000, "RaceBonus": 100000, "RaceBonusTargetPos": 3, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 3, "salary": 6300000, "StartingBonus": 20000, "RaceBonus": 50000, "RaceBonusTargetPos": 4, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "DriverID": 81, "salary": 1500000, "StartingBonus": 20000, "RaceBonus": 50000, "RaceBonusTargetPos": 4, "EndSeason": 2025, "BreakoutClause": 0.5 } ], "Fires": [ { "DriverID": 17, "TeamID": 3, "PosInTeam": 2 }, { "DriverID": 11, "TeamID": 1, "PosInTeam": 2 }, { "DriverID": 116, "TeamID": 6, "PosInTeam": 2 }, { "DriverID": 1, "TeamID": 4, "ExtraTeamID": 1, "PosInTeam": 2 }, { "DriverID": 14, "TeamID": 5, "PosInTeam": 1 }, { "DriverID": 13, "TeamID": 8, "PosInTeam": 1 }, { "DriverID": 95, "TeamID": 3, "PosInTeam": 3 }, { "DriverID": 83, "TeamID": 7, "PosInTeam": 2 }, { "DriverID": 255, "TeamID": 7, "PosInTeam": 1 }, { "DriverID": 105, "TeamID": 9, "PosInTeam": 2 }, { "DriverID": 8, "TeamID": 9, "PosInTeam": 1 }, { "DriverID": 376, "TeamID": 4, "PosInTeam": 5 }, { "DriverID": 135, "TeamID": 5, "PosInTeam": 3 }, { "DriverID": 144, "TeamID": 3, "PosInTeam": 4 }, { "DriverID": 142, "TeamID": 1, "PosInTeam": 3 }, { "DriverID": 279, "TeamID": 2, "PosInTeam": 5 }, { "DriverID": 366, "TeamID": 2, "PosInTeam": 1 }, { "DriverID": 365, "TeamID": 2, "PosInTeam": 1 }, { "DriverID": 6, "TeamID": 1, "PosInTeam": 1 }, { "DriverID": 4, "TeamID": 1, "PosInTeam": 1 }, { "DriverID": 49, "TeamID": 3, "PosInTeam": 2 }, { "DriverID": 37, "TeamID": 3, "PosInTeam": 1 }, { "DriverID": 291, "TeamID": 3, "Retire": 1, "PosInTeam": 1 }, { "DriverID": 335, "TeamID": 10, "PosInTeam": 1 }, { "DriverID": 64, "TeamID": 10, "PosInTeam": 1 }, { "DriverID": 44, "TeamID": 10, "PosInTeam": 1 }, { "DriverID": 39, "TeamID": 5, "PosInTeam": 1 }, { "DriverID": 30, "TeamID": 5, "PosInTeam": 1 }, { "DriverID": 391, "TeamID": 6, "PosInTeam": 1 }, { "DriverID": 60, "TeamID": 8, "PosInTeam": 2 }, { "DriverID": 414, "TeamID": 8, "PosInTeam": 1 }, { "DriverID": 293, "TeamID": 8, "PosInTeam": 1 }, { "DriverID": 299, "TeamID": 9, "PosInTeam": 1 }, { "DriverID": 368, "TeamID": 7, "PosInTeam": 1 }, { "DriverID": 321, "TeamID": 7, "PosInTeam": 1 }, { "DriverID": 58, "TeamID": 7, "PosInTeam": 2 }, { "DriverID": 61, "TeamID": 9, "PosInTeam": 1 }, { "DriverID": 393, "TeamID": 9, "PosInTeam": 2 } ], "Hires": [ { "DriverID": 95, "TeamID": 3, "PosInTeam": 2, "Salary": 800000, "StartingBonus": 20000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 11, "TeamID": 6, "PosInTeam": 2, "Salary": 8000000, "StartingBonus": 200000, "RaceBonus": 100000, "RaceBonusTargetPos": 3, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 1, "TeamID": 1, "PosInTeam": 2, "Salary": 48000000, "StartingBonus": 2000000, "RaceBonus": 1000000, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 376, "TeamID": 4, "PosInTeam": 2, "Salary": 1500000, "StartingBonus": 10000, "RaceBonus": 50000, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5, "GrantsSuperLicense": 1 }, { "DriverID": 135, "TeamID": 5, "PosInTeam": 1, "Salary": 700000, "StartingBonus": 10000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 144, "TeamID": 8, "PosInTeam": 1, "Salary": 700000, "StartingBonus": 10000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 142, "TeamID": 7, "PosInTeam": 1, "Salary": 800000, "StartingBonus": 10000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 14, "TeamID": 7, "PosInTeam": 2, "Salary": 5500000, "StartingBonus": 100000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 83, "TeamID": 9, "PosInTeam": 1, "Salary": 5500000, "StartingBonus": 100000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 279, "TeamID": 9, "PosInTeam": 2, "Salary": 1500000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5, "GrantsSuperLicense": 1 } ], "StaffHires" : [ { "StaffID": 628, "TeamID": 2, "PosInTeam": 1, "Salary": 1500000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 638, "TeamID": 2, "PosInTeam": 1, "Salary": 2500000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 622, "TeamID": 1, "PosInTeam": 1, "Salary": 500000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 623, "TeamID": 3, "PosInTeam": 2, "Salary": 500000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 635, "TeamID": 3, "PosInTeam": 1, "Salary": 700000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2028, "BreakoutClause": 0.5 }, { "StaffID": 639, "TeamID": 3, "PosInTeam": 1, "Salary": 920000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 4, "TeamID": 10, "PosInTeam": 1, "Salary": 1120000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 58, "TeamID": 10, "PosInTeam": 1, "Salary": 1010000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 636, "TeamID": 10, "PosInTeam": 1, "Salary": 940000, "StartingBonus": 70000, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 630, "TeamID": 5, "PosInTeam": 1, "Salary": 640000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 40, "TeamID": 5, "PosInTeam": 1, "Salary": 940000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 624, "TeamID": 7, "PosInTeam": 1, "Salary": 840000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 625, "TeamID": 7, "PosInTeam": 2, "Salary": 839000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 640, "TeamID": 7, "PosInTeam": 1, "Salary": 630000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 626, "TeamID": 8, "PosInTeam": 2, "Salary": 688000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 637, "TeamID": 8, "PosInTeam": 1, "Salary": 488000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 295, "TeamID": 8, "PosInTeam": 1, "Salary": 988000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 633, "TeamID": 6, "PosInTeam": 1, "Salary": 580000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 641, "TeamID": 9, "PosInTeam": 1, "Salary": 550000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 642, "TeamID": 1, "PosInTeam": 1, "Salary": 1650000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "StaffID": 643, "TeamID": 9, "PosInTeam": 1, "Salary": 430000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "StaffID": 644, "TeamID": 9, "PosInTeam": 2, "Salary": 490000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 } ], "Affiliates" : [ { "DriverID": 285, "TeamID": 2, "PosInTeam": 3, "Salary": 500000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "DriverID": 411, "TeamID": 2, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 375, "TeamID": 2, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 399, "TeamID": 2, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 105, "TeamID": 1, "PosInTeam": 3, "Salary": 300000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 20, "TeamID": 1, "PosInTeam": 4, "Salary": 300000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 282, "TeamID": 1, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 419, "TeamID": 1, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 439, "TeamID": 1, "PosInTeam": 7, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "DriverID": 418, "TeamID": 1, "PosInTeam": 8, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 132, "TeamID": 1, "PosInTeam": 9, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 264, "TeamID": 3, "PosInTeam": 3, "Salary": 320000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2027, "BreakoutClause": 0.5 }, { "DriverID": 120, "TeamID": 3, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 245, "TeamID": 3, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 402, "TeamID": 3, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 288, "TeamID": 8, "PosInTeam": 3, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 373, "TeamID": 8, "PosInTeam": 4, "Salary": 150000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2028, "BreakoutClause": 0.5 }, { "DriverID": 379, "TeamID": 8, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 286, "TeamID": 8, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 8, "TeamID": 4, "PosInTeam": 3, "Salary": 1300000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 107, "TeamID": 4, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 610, "TeamID": 4, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 106, "TeamID": 10, "PosInTeam": 3, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 22, "TeamID": 10, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 408, "TeamID": 10, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 409, "TeamID": 10, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 121, "TeamID": 10, "PosInTeam": 7, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 248, "TeamID": 5, "PosInTeam": 3, "Salary": 300000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 398, "TeamID": 5, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 281, "TeamID": 5, "PosInTeam": 5, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 405, "TeamID": 5, "PosInTeam": 6, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 280, "TeamID": 5, "PosInTeam": 7, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 252, "TeamID": 5, "PosInTeam": 8, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 406, "TeamID": 5, "PosInTeam": 9, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 80, "TeamID": 7, "PosInTeam": 3, "Salary": 220000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 247, "TeamID": 6, "PosInTeam": 3, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 }, { "DriverID": 322, "TeamID": 6, "PosInTeam": 4, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2026, "BreakoutClause": 0.5 }, { "DriverID": 401, "TeamID": 9, "PosInTeam": 3, "Salary": 120000, "StartingBonus": 0, "RaceBonus": 0, "RaceBonusTargetPos": 1, "EndSeason": 2025, "BreakoutClause": 0.5 } ], "FeederSeries" : [ { "DriverID": 289, "TeamID": 12, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 119, "TeamID": 12, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 245, "TeamID": 19, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 373, "TeamID": 19, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 288, "TeamID": 16, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 99, "TeamID": 16, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 322, "TeamID": 14, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 282, "TeamID": 14, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 301, "TeamID": 11, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 280, "TeamID": 11, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 121, "TeamID": 18, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 252, "TeamID": 18, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 127, "TeamID": 15, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 377, "TeamID": 15, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 110, "TeamID": 13, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 399, "TeamID": 13, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 378, "TeamID": 17, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 385, "TeamID": 17, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 374, "TeamID": 21, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 390, "TeamID": 21, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 601, "TeamID": 20, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 123, "TeamID": 20, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 602, "TeamID": 22, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 380, "TeamID": 22, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 411, "TeamID": 22, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 610, "TeamID": 23, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 604, "TeamID": 23, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 382, "TeamID": 23, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 381, "TeamID": 24, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 439, "TeamID": 24, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 611, "TeamID": 24, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 283, "TeamID": 28, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 394, "TeamID": 28, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 286, "TeamID": 28, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 375, "TeamID": 25, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 386, "TeamID": 25, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 612, "TeamID": 25, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 379, "TeamID": 27, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 613, "TeamID": 27, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 614, "TeamID": 27, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 615, "TeamID": 26, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 383, "TeamID": 26, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 616, "TeamID": 26, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 384, "TeamID": 29, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 617, "TeamID": 29, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 618, "TeamID": 29, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 619, "TeamID": 31, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 620, "TeamID": 31, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 304, "TeamID": 31, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 406, "TeamID": 30, "PosInTeam": 1, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 388, "TeamID": 30, "PosInTeam": 2, "Salary": 100000, "EndSeason": 2025 }, { "DriverID": 621, "TeamID": 30, "PosInTeam": 3, "Salary": 100000, "EndSeason": 2025 } ] } ================================================ FILE: src/data/members.json ================================================ [ {"name":"Viggo","tier":"backer"}, {"name":"The Logical Bass Fish","tier":"backer"}, {"name":"Kymani Clark","tier":"backer"}, {"name":"zagrebelnio","tier":"backer"}, {"name":"Cian In 't Holt","tier":"founder"}, {"name":"Alessio Romeo","tier":"backer"}, {"name":"Stuka Boy","tier":"backer"}, {"name":"Dave","tier":"backer"}, {"name":"Jaume Carbonell","tier":"founder"}, {"name":"JO96","tier":"backer"}, {"name":"Fynn Lemke","tier":"backer"}, {"name":"Evangelos Boukouris","tier":"backer"}, {"name":"alfonso castillo lemus","tier":"backer"}, {"name":"Diogo","tier":"backer"}, {"name":"Rob","tier":"backer"}, {"name":"Niklas","tier":"backer"}, {"name":"Coen Duggan","tier":"backer"}, {"name":"Flo lp64","tier":"backer"}, {"name":"World By Adam","tier":"backer"}, {"name":"Cardemir","tier":"backer"}, {"name":"Florian Paul","tier":"backer"}, {"name":"Kakub","tier":"backer"}, {"name":"Håvard Fredriksen","tier":"backer"}, {"name":"WHO IS MINICO?","tier":"backer"}, {"name":"Daniel Esterson","tier":"backer"}, {"name":"Lyliaan","tier":"backer"}, {"name":"Lukus Wright","tier":"backer"}, {"name":"retrop","tier":"backer"}, {"name":"Duarte Sardinha","tier":"backer"}, {"name":"Jose","tier":"backer"}, {"name":"Kevin Peltser","tier":"supporter"}, {"name":"Xabier","tier":"backer"}, {"name":"Khan Goatman","tier":"backer"}, {"name":"ccesvin","tier":"backer"}, {"name":"Artyom Sizov","tier":"supporter"}, {"name":"Commando1180","tier":"backer"}, {"name":"영민 이","tier":"backer"}, {"name":"Happy Oriley","tier":"backer"}, {"name":"Alexander Udeaja","tier":"backer"}, {"name":"Mackle","tier":"backer"}, {"name":"Eetu Väisänen","tier":"backer"}, {"name":"용준 김","tier":"backer"}, {"name":"Kevin Szilagyi","tier":"backer"}, {"name":"스tv 빅","tier":"supporter"}, {"name":"Jack Sives","tier":"backer"}, {"name":"Kunal Shah","tier":"supporter"}, {"name":"Wan Muhammad","tier":"supporter"}, {"name":"aalyx","tier":"backer"}, {"name":"JARNCJ","tier":"backer"}, {"name":"Shadow 2Jz 99","tier":"backer"}, {"name":"Vladuna","tier":"backer"}, {"name":"Tom","tier":"backer"}, {"name":"Schnupsi Dupsi","tier":"supporter"}, {"name":"Ian Ang","tier":"backer"}, {"name":"ysty04","tier":"founder"}, {"name":"선호 김","tier":"supporter"}, {"name":"Bruno Piris","tier":"backer"}, {"name":"YannAsdelion","tier":"supporter"}, {"name":"Eee","tier":"backer"}, {"name":"海宝賢一郎","tier":"supporter"}, {"name":"Brian Ferguson","tier":"backer"}, {"name":"Michael","tier":"backer"}, {"name":"RageEliteX","tier":"supporter"}, {"name":"Ericthebest","tier":"supporter"}, {"name":"Djocyk","tier":"supporter"}, {"name":"alex browne","tier":"supporter"}, {"name":"david garcia","tier":"supporter"}, {"name":"Joshua Herreen","tier":"backer"}, {"name":"SpeedCB28","tier":"backer"}, {"name":"Q9R","tier":"supporter"}, {"name":"Pepelino55","tier":"backer"}, {"name":"gspintel.","tier":"supporter"}, {"name":"MSC_SchumiWM2022","tier":"backer"}, {"name":"Kold","tier":"founder"}, {"name":"Darren2ickn9r","tier":"founder"}, {"name":"Jaume sl23","tier":"supporter"}, {"name":"Michael Parchaiski","tier":"backer"}, {"name":"Martin Schwingeweitzen","tier":"supporter"}, {"name":"Friedemann Der 1.","tier":"backer"}, {"name":"Billy Manson","tier":"backer"}, {"name":"matteo fournier","tier":"backer"}, {"name":"Abner Chao","tier":"backer"}, {"name":"katon","tier":"backer"}, {"name":"Wild Buzzer","tier":"backer"}, {"name":"LilHowza","tier":"supporter"}, {"name":"Carl Robinson","tier":"backer"}, {"name":"Anthony D'Amico","tier":"supporter"}, {"name":"Matej Lhotsky","tier":"backer"}, {"name":"Sarah Gregory","tier":"backer"}, {"name":"Bailey Sprecher","tier":"backer"}, {"name":"metin","tier":"backer"}, {"name":"Michael Gabriel","tier":"founder"}, {"name":"Nuttybong","tier":"backer"}, {"name":"Honk_04","tier":"supporter"}, {"name":"LGFT","tier":"backer"}, {"name":"Delay Delama","tier":"founder"}, {"name":"Odradek","tier":"backer"}, {"name":"Leikjarinn","tier":"founder"}, {"name":"Erik Spiering","tier":"founder"}, {"name":"JKing","tier":"backer"}, {"name":"Thomas","tier":"backer"}, {"name":"Brandon","tier":"backer"}, {"name":"Maikel Zwart","tier":"supporter"}, {"name":"William Slocombe","tier":"backer"}, {"name":"Shaun Thomas","tier":"backer"}, {"name":"Hadock","tier":"backer"}, {"name":"Joe","tier":"supporter"}, {"name":"Robin Rößler","tier":"supporter"}, {"name":"lee","tier":"backer"}, {"name":"Jack Alexander Naylor","tier":"backer"}, {"name":"Richard Hicks","tier":"founder"}, {"name":"BleedinEdge821","tier":"backer"}, {"name":"Kyle","tier":"supporter"}, {"name":"Old Mate Johno","tier":"backer"}, {"name":"Justin Gärtner","tier":"backer"}, {"name":"Krispy~","tier":"backer"}, {"name":"Sabrina Almer","tier":"backer"}, {"name":"Patryk Gadomski","tier":"founder"}, {"name":"aarava","tier":"supporter"}, {"name":"MickHulst","tier":"backer"}, {"name":"Cm382714","tier":"backer"}, {"name":"Robbles Quin","tier":"backer"}, {"name":"Qingou Liu","tier":"backer"}, {"name":"Kevin Brandsborg","tier":"backer"}, {"name":"Kevin Brogan","tier":"supporter"}, {"name":"Robin","tier":"supporter"}, {"name":"Axel Ravix","tier":"backer"}, {"name":"Cole Pelzer","tier":"backer"}, {"name":"kotemen89","tier":"backer"}, {"name":"Robin Frisk","tier":"backer"}, {"name":"senweg888","tier":"founder"}, {"name":"Jack Matthews","tier":"backer"}, {"name":"lovetoojacket","tier":"supporter"}, {"name":"Ryan","tier":"supporter"}, {"name":"Ross Patterson","tier":"backer"}, {"name":"Mark Wright","tier":"backer"}, {"name":"Sam_Fakt","tier":"founder"}, {"name":"Enhabe","tier":"backer"}, {"name":"Marcus Miller","tier":"founder"}, {"name":"Oliver Nelson","tier":"supporter"}, {"name":"FirearmofMutiny","tier":"supporter"}, {"name":"ollie","tier":"backer"}, {"name":"Harry Willetts","tier":"founder"}, {"name":"Roberto Cruz","tier":"backer"}, {"name":"Kaiowa McAninly","tier":"backer"}, {"name":"Ethan Tapa","tier":"founder"}, {"name":"tornado_ow","tier":"founder"}, {"name":"Anthony Taylor","tier":"backer"}, {"name":"averstance","tier":"backer"}, {"name":"Justin Logan","tier":"backer"}, {"name":"Noel Hernandez","tier":"founder"}, {"name":"Peregrin","tier":"founder"}, {"name":"Jayden Martis","tier":"backer"}, {"name":"Marcus Deamon","tier":"backer"}, {"name":"Aaron655","tier":"founder"}, {"name":"Parker Petrov","tier":"backer"} ] ================================================ FILE: src/data/news/news_prompts_templates.json ================================================ [ { "id": "quali_result", "new_type": 1, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} summarizing the qualifying session of the {{circuit}} Grand Prix in {{season_year}} where {{pole_driver}} got pole position. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Focus on the results of the qualifying session and the starting grid, highlighting the pole position and the top qualifiers. Mention the key moments that determined the final positions, if provided, but do not speculate beyond the data given. Describe how the pole-sitter achieved the fastest time and highlight any unexpected performances or upsets. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "race_result", "new_type": 2, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} summarizing the win that {{winner}} achieved at the {{circuit}} Grand Prix in {{season_year}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Focus on the race results and championship implications, but do not assume how dominant or dramatic the race was unless the gaps clearly suggest it. Mention the top finishers in a narrative way, without listing all drivers or repeating every time gap. Instead of exact time gaps with decimals, use natural phrases like “a few seconds ahead”, “closely followed by”, or “just behind”. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "silly_season_recap", "new_type": 4, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} summarizing the current state of the {{season}} F1 silly season. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long. Focus on the driver market, mentioning key drivers who are rumored to be moving teams, and any confirmed moves. Discuss the implications of these changes for the teams and drivers involved. When talking about salaries, do not give overly specific figures, but rather discuss approximate amounts with a round number. Vary the phrasing when describing which seats are being targeted, so it does not sound repetitive. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "made_up_rumor", "new_type": 7, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} covering a rumor about {{driver1}} potentially leaving {{team1}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Clearly indicate that the information is speculative and based on paddock gossip. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Discuss why this rumor has surfaced, mentioning any factors that might contribute to {{driver1}} considering a move. Highlight any potential destinations that have been suggested, but avoid stating anything as confirmed. Balance the tone between curiosity and skepticism, as the rumor has not been verified. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "big_transfer", "new_type": 6, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} confirming the big transfer of {{driver1}} to {{team2}} from {{team1}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Focus on the details of the transfer, including the reasons behind it and the implications for both the driver and the team. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "potential_champion", "new_type": 8, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about {{driver_name}} having the potential to become the {{season_year}} Formula 1 World Champion at the upcoming {{circuit}} Grand Prix. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Explain the conditions under which {{driver_name}} would secure the championship at this race against {{rival_driver_name}}, considering the points currently held by both ({{driver_points}} vs {{rival_points}})." }, { "id": "world_champion", "new_type": 9, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about {{driver_name}} winning the {{season_year}} Formula 1 World Championship at the {{circuit}} Grand Prix. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Summarize how {{driver_name}} clinched the championship, detailing their performance throughout the season and the key moments that led to this achievement. Mention the final points tally, comparing it to their closest rival, {{rival_driver_name}}, and highlight any significant result differences. Discuss the implications of this victory for both {{driver_name}} and their team, including any records set or milestones achieved. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "contract_renewal", "new_type": 10, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about {{driver1}} renewing their contract with {{team1}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Focus on the details of the contract renewal, including the duration of the new contract and any improvements in terms or conditions. Discuss the reasons behind {{driver1}}'s decision to stay with {{team1}}, highlighting their performance and relationship with the team. Mention any statements from {{driver1}} or team representatives regarding the renewal. When talking about salaries, do not give overly specific figures, but rather discuss approximate amounts with a round number." }, { "id": "team_comparison_bad", "new_type": 11, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about {{team1}}'s performance in the {{actualSeason}} Formula 1 season so far, comparing it to their better {{lastSeason}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long. Explain with all the info given why they are scoring fewer points this season. Use a structured but flowing narrative, as in a typical F1 news report." }, { "id": "team_comparison_good", "new_type": 12, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about {{team1}}'s performance in the {{actualSeason}} Formula 1 season so far, comparing it to their worse {{lastSeason}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long. Explain with all the info given why they are scoring more points this season. Use a structured but flowing narrative, as in a typical F1 news report." }, { "id": "driver_comparison", "new_type": 13, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} comparing the performances of {{driver1}} and {{driver2}} from {{team1}} in the {{actualSeason}} Formula 1 season so far. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long. Analyze their performances based on the provided statistics, including points scored, qualifying positions, and race results. Compare how many times each driver has outqualified the other and their and how many times each has finished ahead of the other." }, { "id": "season_review", "new_type": 14, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} reviewing the {{season_year}} Formula 1 season so far, after the {{part}} third of the season has gone. {{driver1}} is leading the Drivers Championship followed by {{driver2}}, and {{team1}} is currently leading the Constructors Championship. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long. Summarize the key events of the season, including the championship battle, standout performances by drivers and teams, and any significant controversies or developments. Highlight the main storylines that defined the season and discuss their implications for the future of Formula 1. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "season_review_end", "new_type": 15, "prompt": "Write ONLY the body (not the title) of a long, structured {{language}} news article reviewing the recently concluded {{season_year}} Formula 1 season. {{driver1}} won the Drivers’ Championship, followed by {{driver2}}, while {{team1}} secured the Constructors’ Championship.\n\nThe article should be written in a professional, analytical tone similar to Autosport or The Race — insightful, critical when needed, and entirely journalistic. Avoid any exaggerated language or fan-like tone.\n\nUse clear Markdown section headers (e.g. '# Season Overview', '## Winners and Strong Performers', '## Disappointments and Underperformers', '## Key Storylines', '## Final Thoughts'). Each section should include multiple paragraphs with coherent flow and strong analytical depth.\n\nYour analysis must:\n- Be between 1000 and 1300 words.\n- Discuss the championship battle, turning points, standout races, and key controversies.\n- Evaluate both drivers and teams, distinguishing between those who exceeded expectations and those who fell short.\n- Provide a critical, balanced assessment — not everything needs to be positive. Highlight underperforming drivers even within strong teams.\n- Include direct intra-team comparisons when relevant (e.g. if one driver significantly outperformed their teammate, or if a veteran was outshined by a newcomer).\n- Avoid generic praise: always explain *why* a driver or team performed well or poorly.\n- End with a reflective section summarizing what the {{season_year}} season revealed about the competitive order and what might lie ahead for next year.\n\nMaintain a natural, flowing journalistic style with paragraph transitions, factual grounding, and the nuance expected from a professional F1 outlet." }, { "id": "race_reaction", "new_type": 16, "prompt": "Write ONLY the body (not the title) of a detailed {{language}} news article compiling the post-race reactions from the {{season_year}} {{adjective}} Grand Prix at {{circuit}}. The article should include comments from both the most important drivers at {{adjective}} GP, as well as the top drivers in the championship.\n\nWrite in a professional and authentic journalistic tone similar to Autosport, The Race, or Motorsport.com — informative, balanced, and realistic. Avoid exaggerated or fan-like language. The goal is to recreate a genuine media article covering the emotional and analytical side of the race aftermath.\n\nUse **Markdown** for structure:\n- Begin with a short introductory paragraph summarizing the main talking points of the {{adjective}} GP — key results and the general mood in the paddock.\n- Then, divide the rest of the article into clear sections by driver using Markdown headings (e.g. '## {{happy_driver}}', '## {{unhappy_driver}}', '## Other Reactions'). Each section should include multiple paragraphs mixing direct quotes, paraphrased remarks, and short context about each driver's race.\n\nYour article must:\n- Be as long as 1200 words.\n- Use realistic, natural-sounding driver quotes (in quotation marks) blended into journalistic narration.\n- Capture a range of emotions: joy, frustration, relief, disappointment, pride, etc.\n- Mention contextual details such as the race result, strategy, mistakes, team performance, and weather or track conditions if relevant.\n- Ensure transitions between sections are smooth and professional.\n- Avoid cliché or repetitive expressions like 'amazing race' or 'we gave 110%'; aim for natural, grounded dialogue.\n\nMaintain a professional, immersive tone throughout, as if written for a top-tier F1 publication immediately after the race weekend." }, { "id": "massive_exit", "new_type": 18, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} reporting that {{team1}} has confirmed that {{driver1}} will leave their team at the end of the season. It is still uncertain of where will {{driver1}} will go.. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 350 and 450 words long. Focus on the details of the transfer, including the reasons behind it and the implications for both the driver and the team. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "next_season_grid", "new_type": 19, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} previewing the driver lineup for the upcoming {{season_year}} Formula 1 season. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 700 and 800 words long. Focus on the confirmed driver-team pairings for the new season, highlighting any significant changes from the previous year. Discuss the implications of these changes for both the drivers and the teams involved. Mention any notable rookies or returning drivers, as well as any shifts in team dynamics that may impact performance. Use a flowing, paragraph-based structure like a typical F1 news report." }, { "id": "feeder_series_review", "new_type": 20, "prompt": "Write ONLY the body (not the title) of a professional season review article in {{language}} summarizing the {{season_year}} Formula 2 and Formula 3 seasons. In Formula 2, {{f2_champion}} won the championship, and in Formula 3, {{f3_champion}} claimed the title.\n\nThe article must be written in clear, natural {{language}}, using a neutral and professional journalistic tone similar to outlets such as Autosport or Motorsport.com. Avoid slang, hype, or overly dramatic language.\n\nThis article must function as a long-term historical recap of both seasons, suitable for readers who want to understand what happened in Formula 2 and Formula 3 during {{season_year}}. For each category, include:\n- An overview of how the season unfolded\n- Key championship battles and decisive moments\n- A summary of race winners across the different Grands Prix\n- Notable performances, consistency trends, and turning points\n- How and why the eventual champion secured the title\n\nDo not invent events or results. Focus on clarity, structure, and factual-style narration. The article should read as a comprehensive end-of-season review rather than a single race report.\n\nMaximum length: 1000 words." } ] ================================================ FILE: src/data/news/news_titles_templates.json ================================================ [ { "id": "quali_result", "new_type": 1, "titles": [ "{{pole_driver}} takes pole position at {{circuit}} after a thrilling qualifying", "{{pole_driver}} secures P1 in {{country}} following intense final laps", "Pole for {{pole_driver}} after a masterful qualifying at the {{adjective}} Grand Prix", "{{pole_driver}} delivers under pressure to take pole at {{circuit}}", "Stunning lap from {{pole_driver}} secures top spot at {{circuit}}", "{{pole_driver}} sets the benchmark with a blistering lap in {{country}} qualifying", "Unstoppable {{pole_driver}} dominates qualifying at the {{adjective}} GP", "Qualifying masterclass — {{pole_driver}} claims pole at {{circuit}}", "{{pole_driver}} edges out rivals to take pole at {{circuit}}", "Dominant performance from {{pole_driver}} in {{country}} qualifying session", "Precision and pace — {{pole_driver}} delivers pole at the {{adjective}} Grand Prix", "Blistering pace from {{pole_driver}} locks in P1 at the {{adjective}} GP qualifying", "{{pole_driver}} beats the clock to secure pole in {{country}}", "Nail-biting finish sees {{pole_driver}} grab pole at {{circuit}}", "Perfect lap from {{pole_driver}} earns pole at the {{adjective}} GP", "{{pole_driver}} steals the show in {{country}} qualifying session", "{{pole_driver}} takes control of the grid with pole at {{circuit}}", "Late heroics from {{pole_driver}} seal pole position at {{circuit}}", "{{pole_driver}} pulls off stunning lap to top the {{adjective}} Grand Prix qualifying", "A statement from {{pole_driver}} — pole secured at {{circuit}}", "Final moments drama — {{pole_driver}} edges out rivals for P1 at {{circuit}}", "{{pole_driver}} delivers when it matters most to clinch pole at {{circuit}}", "Flawless execution from {{pole_driver}} leads to pole in {{country}}", "Another qualifying masterclass — {{pole_driver}} starts from pole at the {{adjective}} GP" ] }, { "id": "race_result", "new_type": 2, "titles": [ "{{winner}} wins the {{season_year}} {{adjective}} Grand Prix after an intense battle", "Victory for {{winner}} at {{circuit}} – a flawless performance from lights to flag", "{{winner}} triumphs in {{country}} after a thrilling race day", "{{winner}} takes the checkered flag at {{circuit}} with dominant pace", "{{winner}} secures a commanding win at the {{adjective}} GP", "{{winner}} celebrates victory in {{country}} after a dramatic final stint", "Dominant drive – {{winner}} takes victory at {{circuit}}", "{{winner}} comes out on top at the {{adjective}} Grand Prix after tense strategy calls", "{{winner}} finishes first at {{circuit}} in style", "{{winner}} grabs victory in {{country}} following a nail-biting finale", "Clinical drive from {{winner}} secures top spot at {{circuit}}", "Perfect execution from {{winner}} brings home the win at {{country}}", "{{winner}} dominates the {{circuit}} race to claim victory", "A stunning performance – {{winner}} claims victory at the {{adjective}} GP", "Masterclass drive from {{winner}} at the {{adjective}} Grand Prix", "{{winner}} delivers an unstoppable drive to victory in {{country}}", "{{winner}} crosses the line first after a thrilling race at {{circuit}}", "{{winner}} proves unstoppable with a commanding win at the {{adjective}} GP", "From lights out to victory – {{winner}} reigns supreme at {{circuit}}", "{{winner}} holds firm under pressure to claim victory in {{country}}", "Impeccable strategy pays off – {{winner}} wins the {{adjective}} GP", "No one could stop {{winner}} – dominant at the {{adjective}} Grand Prix", "{{winner}} takes the win at {{circuit}} after a perfect execution" ] }, { "id": "silly_season_recap", "new_type": 4, "titles": [ "Silly Season chaos – {{driver1}}, {{driver2}}, and {{driver3}} linked with surprise moves", "Driver market shake-up – {{driver1}} and {{driver2}} in talks with {{team1}} and {{team2}}", "Rumors fly as {{driver1}}, {{driver2}}, and {{driver3}} consider team changes", "{{driver1}} and {{driver2}} at the center of silly season speculation", "Driver carousel turning fast – {{driver1}}, {{driver2}}, and {{driver3}} all in the mix", "Big names on the move – {{driver1}} and {{driver2}} reportedly in discussions with {{team1}}", "The paddock buzzes – {{driver1}} and {{driver3}} linked with major team switches", "Transfer dominoes ready to fall – {{driver1}}, {{driver2}}, and {{driver3}} headline silly season", "Driver market heating up – {{driver1}}, {{driver2}}, and {{driver3}} could be on the move", "{{team1}} and {{team2}} in the spotlight as {{driver1}} and {{driver2}} explore new options", "No one safe – {{driver1}}, {{driver2}}, and {{driver3}} all connected to potential moves", "Changing seats – {{driver1}} and {{driver2}} rumored to switch teams for next season", "BREAKING NEWS – {{driver1}} and {{driver2}} exploring offers from {{team1}} and {{team2}}", "Silly Season madness continues – {{driver1}}, {{driver2}}, and {{driver3}} part of ongoing rumors", "{{driver1}} and {{driver2}} shake up the market with talks involving {{team1}} and {{team2}}", "Sources point to multiple driver swaps involving {{driver1}}, {{driver2}}, and {{driver3}}", "Teams play musical chairs – {{driver1}}, {{driver2}}, and {{driver3}} could all move", "{{driver1}}’s name surfaces again as silly season drama unfolds", "Major shake-up coming? {{driver1}}, {{driver2}}, and {{driver3}} under contract speculation", "HERE WE GO! Silly Season intensifies with {{driver1}}, {{driver2}}, and {{driver3}} in the rumor mill", "{{driver1}} and {{driver2}} evaluating options as silly season enters crucial phase", "Driver market chaos – {{driver1}} and {{driver2}} linked to shock transfers", "Silly Season rolls on – {{driver1}} and {{driver3}} emerging as key figures", "F1 gossip grows – {{driver1}} and {{driver2}} reportedly negotiating with {{team1}} and {{team2}}", "The paddock in motion – {{driver1}}, {{driver2}}, and {{driver3}} eye new opportunities" ] }, { "id": "big_transfer_rumor", "new_type": 5, "titles": [ "BREAKING NEWS – {{driver1}} in advanced talks to join {{team1}}", "Major rumor – {{driver1}} could replace key seat at {{team1}}", "Sources suggest {{driver1}} to {{team1}} move nearing agreement", "Shock in the paddock – {{driver1}} linked with {{team1}} for next season", "Insiders claim {{driver1}} preparing to leave {{originalTeam}} for {{team1}}", "Could {{driver1}} be switching from {{originalTeam}} to {{team1}}? Signs point that way", "{{team1}} ready to secure {{driver1}} in potential high-profile move", "Big rumor – {{driver1}} leaving {{originalTeam}} for {{team1}} gaining momentum", "Reports indicate {{driver1}} already in talks with {{team1}} over a future seat", "{{driver1}} ready to leave {{originalTeam}} - {{team1}} emerges as top destination", "{{driver1}} expected to join {{team1}} as talks intensify behind the scenes", "{{team1}} and {{driver1}} closing in on surprise agreement", "BREAKING – {{driver1}} to {{team1}} deal reportedly in final stages", "{{driver1}} considering a switch to {{team1}} after internal tensions at {{originalTeam}}", "Multiple sources confirm {{driver1}} to {{team1}} rumor gaining traction", "Silly Season twist – {{team1}} emerges as frontrunner to sign {{driver1}}", "{{driver1}}’s future uncertain as {{team1}} move looms large", "The paddock reacts – {{driver1}} strongly linked to {{team1}}", "{{team1}} reportedly preparing formal offer for {{driver1}}", "{{driver1}} to {{team1}} rumor reignites amid contract silence at {{originalTeam}}", "Deal close – {{driver1}} expected to race for {{team1}} soon", "Sources claim {{driver1}} and {{team1}} have reached verbal understanding", "Big move incoming – {{driver1}} to {{team1}} looking more likely by the day", "Speculation mounts – {{driver1}} could replace outgoing driver at {{team1}}", "{{driver1}} to {{team1}} rumor gains strength after latest paddock reports", "Transfer talk intensifies – {{driver1}} and {{team1}} linked again ahead of next season" ] }, { "id": "big_transfer", "new_type": 6, "titles": [ "It’s official – {{driver1}} signs with {{team2}}", "Transfer confirmed – {{driver1}} leaves {{team1}} for {{team2}}", "{{team2}} proudly announces the arrival of {{driver1}}", "{{driver1}} joins {{team2}} for the upcoming season", "DONE DEAL – {{driver1}} completes move to {{team2}}", "Confirmed – {{driver1}} switches from {{team1}} to {{team2}}", "HERE WE GO – {{driver1}} to {{team2}} finally confirmed", "BREAKING NEWS – {{driver1}} officially joins {{team2}}", "{{team2}} welcomes {{driver1}} to the team", "Transfer news – {{driver1}} officially signs with {{team2}}", "Big announcement – {{driver1}} will race for {{team2}} next season", "HERE WE GO – {{driver1}} to {{team2}} is now official", "{{driver1}} moves to {{team2}} after successful negotiations", "Confirmed move – {{driver1}} set to drive for {{team2}} in the new season", "{{team2}} confirms {{driver1}} as their latest signing", "Major signing – {{driver1}} joins {{team2}} for {{season_year}}", "{{driver1}} leaves {{team1}} and joins {{team2}} in a high-profile transfer", "DONE DEAL – {{driver1}} unveiled as new {{team2}} driver", "{{team2}} finalizes deal with {{driver1}} for the upcoming campaign", "{{driver1}} officially announced as part of {{team2}}’s lineup", "It’s done – {{driver1}} signs multi-year deal with {{team2}}", "Contract signed – {{driver1}} will compete with {{team2}} starting {{season_year}}", "Official statement – {{team2}} confirms arrival of {{driver1}}", "HERE WE GO – {{driver1}} joins {{team2}} after weeks of speculation", "The wait is over – {{driver1}} officially a {{team2}} driver" ] }, { "id": "made_up_rumour", "new_type": 7, "titles": [ "EXCLUSIVE - {{driver1}} in talks to leave {{team1}}", "Is {{driver1}} considering an exit from {{team1}}?", "Rumor alert - {{driver1}} eyeing a move away from {{team1}}", "Sources say {{driver1}} is eyeing options beyond {{team1}}", "Insider info - {{driver1}} contemplating a switch from {{team1}}", "Speculation mounts - {{driver1}} could be on the move from {{team1}}", "Driver market buzz - {{driver1}} linked with a potential exit from {{team1}}", "Could {{driver1}} be leaving {{team1}}?", "{{driver1}} unhappy at {{team1}}? Rumors suggest a possible exit", "BREAKING – {{driver1}} reportedly unhappy at {{team1}}", "Rumor alert – {{driver1}} considering a move away from {{team1}}", "Speculation grows around {{driver1}}’s future at {{team1}}", "BREAKING - {{driver1}} ready to part ways with {{team1}}", "'It's getting frustrating' – {{driver1}} prepared to leave {{team1}}", "Paddock talk – {{driver1}} eyeing possible exit from {{team1}}", "Reports suggest {{driver1}} may be exploring other options outside {{team1}}", "{{driver1}} linked with potential departure from {{team1}}", "Sources indicate {{driver1}} could leave {{team1}} at season’s end", "Tension rising? {{driver1}} rumored to be frustrated within {{team1}}", "Uncertainty surrounds {{driver1}} as rumors of exit from {{team1}} intensify", "Media reports hint at {{driver1}} evaluating future away from {{team1}}", "Paddock insiders claim {{driver1}} no longer comfortable at {{team1}}", "Could {{driver1}} be preparing to leave {{team1}}? Rumors suggest so", "The paddock is talking – {{driver1}}’s situation at {{team1}} under scrutiny", "Breaking reports – {{driver1}} allegedly exploring options beyond {{team1}}", "Early rumors connect {{driver1}} to alternative seat away from {{team1}}", "Reports emerge suggesting {{driver1}} open to new challenges beyond {{team1}}", "Sources unsure about {{driver1}}’s future as {{team1}} tensions rise" ] }, { "id": "potential_champion", "new_type": 8, "titles": [ "{{driver_name}} on the brink of glory – can they clinch the title at {{circuit}}", "Championship countdown – {{driver_name}} could secure the crown in {{country}}", "All eyes on {{driver_name}} as they chase the world title at the {{adjective}} Grand Prix", "{{driver_name}} just one step away from the championship at {{circuit}}", "Title permutations – {{driver_name}} poised for glory at the {{adjective}} GP", "{{driver_name}} eyes early championship celebration at {{circuit}}", "Can {{driver_name}} seal the deal – championship on the line in {{country}}", "{{driver_name}} approaches title decider at the {{adjective}} Grand Prix", "One hand on the trophy – {{driver_name}} could be crowned at {{circuit}}", "{{driver_name}} has a chance to wrap up the championship in {{country}}", "History beckons – {{driver_name}} can clinch the title at {{circuit}}", "The moment of truth – {{driver_name}} could become champion at the {{adjective}} GP", "Glory within reach – {{driver_name}} heads into {{circuit}} with title hopes alive", "{{driver_name}} eyes world championship glory in {{country}}", "Crucial weekend ahead – {{driver_name}} could secure the title at {{circuit}}", "{{driver_name}} set for potential championship celebration at the {{adjective}} GP", "Mathematical chance – {{driver_name}} could seal the title in {{country}}", "{{driver_name}} closing in on championship glory at {{circuit}}", "Title tension rising – {{driver_name}} can finish the job at the {{adjective}} Grand Prix", "{{driver_name}} ready to make history at {{circuit}} if results go their way", "Everything to play for – {{driver_name}} could clinch the title this weekend", "{{driver_name}} on the verge of sealing the championship in {{country}}", "The crown within reach – {{driver_name}} faces decisive {{adjective}} GP", "{{driver_name}} could become world champion at {{circuit}} depending on results", "Title in sight – {{driver_name}} enters {{country}} weekend with everything to gain" ] }, { "id": "world_champion", "new_type": 9, "titles": [ "{{driver_name}} crowned World Champion at the {{adjective}} Grand Prix", "History made – {{driver_name}} secures World Championship title in {{country}}", "Glory for {{driver_name}} – crowned champion at {{circuit}}", "{{driver_name}} celebrates World Championship victory in {{country}}", "Unstoppable {{driver_name}} – the new World Champion", "{{driver_name}} clinches the World Drivers’ Championship at {{circuit}}", "It’s done – {{driver_name}} becomes World Champion at the {{adjective}} GP", "BREAKING NEWS – {{driver_name}} wins the World Championship at {{circuit}}", "World Champion – {{driver_name}} takes the crown after the {{adjective}} Grand Prix", "{{driver_name}} achieves ultimate glory with title win in {{country}}", "Championship glory – {{driver_name}} officially crowned World Champion", "Dominant season rewarded – {{driver_name}} secures the title in {{country}}", "{{driver_name}} makes history with championship triumph at {{circuit}}", "The dream achieved – {{driver_name}} becomes World Champion", "Pure emotion – {{driver_name}} celebrates title win at the {{adjective}} GP", "{{driver_name}} joins the legends after clinching the title in {{country}}", "Unforgettable moment – {{driver_name}} takes championship glory at {{circuit}}", "Championship secured – {{driver_name}} crowned king of Formula 1", "{{driver_name}} seals the deal – now World Champion at the {{adjective}} GP", "From contender to champion – {{driver_name}} completes incredible season", "BREAKING – {{driver_name}} wins the World Championship after dramatic race in {{country}}", "A new era begins – {{driver_name}} takes the throne at {{circuit}}", "Historic day – {{driver_name}} crowned champion after the {{adjective}} Grand Prix", "The crown belongs to {{driver_name}} – World Champion at {{circuit}}", "What a season – {{driver_name}} takes the title and makes history" ] }, { "id": "contract_renewal", "new_type": 10, "titles": [ "OFFICIAL – {{driver1}} extends contract with {{team1}}", "Breaking – {{driver1}} signs new deal with {{team1}}", "Contract renewal confirmed – {{driver1}} stays with {{team1}}", "{{driver1}} commits future to {{team1}} with extended agreement", "It’s official – {{driver1}} renews contract with {{team1}}", "Long-term deal signed – {{driver1}} remains with {{team1}}", "{{driver1}} and {{team1}} agree to multi-year contract extension", "Confirmed – {{driver1}} continues racing with {{team1}}", "New chapter – {{driver1}} stays at {{team1}} after signing fresh deal", "Good news for fans – {{driver1}} extends stay with {{team1}}", "OFFICIAL – {{driver1}} renews contract and remains part of {{team1}}’s lineup", "Big announcement – {{driver1}} and {{team1}} continue partnership", "Confirmed extension – {{driver1}} to stay with {{team1}} beyond this season", "{{driver1}} secures new deal to continue with {{team1}}", "{{team1}} confirms contract renewal for {{driver1}}", "Mutual confidence – {{driver1}} and {{team1}} extend collaboration", "Happy to stay – {{driver1}} signs new contract with {{team1}}", "{{driver1}} remains loyal to {{team1}} with fresh multi-year deal", "OFFICIAL NEWS – {{driver1}} continues with {{team1}} for upcoming seasons", "{{team1}} announces renewed deal with {{driver1}}", "Stability confirmed – {{driver1}} stays at {{team1}} after new agreement", "New deal in place – {{driver1}} continues with {{team1}}", "Commitment extended – {{driver1}} and {{team1}} strengthen their partnership", "Confirmed – {{driver1}} to remain with {{team1}} into the next season", "OFFICIAL – {{driver1}} renews contract and extends stay at {{team1}}" ] }, { "id": "team_comparison_bad", "new_type": 11, "titles": [ "What’s going wrong at {{team1}} – performance decline continues", "Pressure mounting at {{team1}} after another disappointing weekend", "Inside {{team1}} – concerns grow as results fail to improve", "{{team1}} struggling to match last year’s form", "The situation at {{team1}} – tough season exposes key weaknesses", "Setbacks for {{team1}} as performance drops below expectations", "Tough times for {{team1}} – questions raised over development path", "Struggles continue for {{team1}} as results disappoint again", "Can {{team1}} turn things around – another poor showing raises doubts", "{{team1}} faces internal pressure amid ongoing performance issues", "The pressure is on – {{team1}} unable to replicate last season’s pace", "{{team1}} under scrutiny after series of underwhelming results", "What’s behind {{team1}}’s recent decline in performance", "Frustration grows inside {{team1}} as rivals pull ahead", "The downfall of {{team1}} – from contender to midfield struggler", "{{team1}} faces tough questions after another weak weekend", "Worrying trend – {{team1}} continues to fall behind competition", "Time running out for {{team1}} to fix their performance issues", "Critical period for {{team1}} as development struggles persist", "Pressure builds within {{team1}} – disappointing results spark concern", "Another setback – {{team1}} fails to deliver improvements", "{{team1}}’s season unravels after difficult run of form", "Uncertain future for {{team1}} – form slump continues", "{{team1}} must find answers quickly after latest poor showing", "Alarm bells ringing – {{team1}} slipping further down the order" ] }, { "id": "team_comparison_good", "new_type": 12, "titles": [ "Positive momentum building at {{team1}} after strong performances", "Encouraging signs – {{team1}} showing real progress this season", "Inside {{team1}} – confidence rising after steady improvement", "The turnaround continues – {{team1}} impressing with recent results", "Progress for {{team1}} as upgrades begin to deliver performance", "Optimism grows at {{team1}} following consistent results", "Strong progress – {{team1}} emerging as a surprise of the season", "Big step forward for {{team1}} after months of hard work", "{{team1}} begins to close the gap to front-runners with improved pace", "The rebuild paying off – {{team1}} showing clear signs of recovery", "Upward trend – {{team1}} finding form and confidence again", "Encouraging weekend for {{team1}} – steady progress continues", "After a difficult year, {{team1}} finally back on the right path", "Progress report – {{team1}} moving closer to the top", "{{team1}} making strides after successful development updates", "Positive results boost morale at {{team1}}", "Consistency returns – {{team1}} looks stronger with each race", "Step by step – {{team1}} climbing back up the grid", "Encouraging pace from {{team1}} signals brighter future ahead", "{{team1}} begins to surprise rivals with improved performance", "Confidence growing – {{team1}} showing they belong among the top teams", "Signs of revival – {{team1}} continuing to impress the paddock", "Renewed energy at {{team1}} as progress becomes visible", "{{team1}} turning heads after exceeding early expectations", "From struggle to strength – {{team1}}’s comeback gathers pace" ] }, { "id": "driver_comparison", "new_type": 13, "titles": [ "{{driver1}} outperforms {{driver2}} at {{team}} after another strong weekend", "{{team}} – {{driver1}} continues to lead the internal battle over {{driver2}}", "{{driver1}} showing stronger form than {{driver2}} as the season unfolds", "Performance gap widening between {{driver1}} and {{driver2}} at {{team}}", "{{driver1}} shines while {{driver2}} struggles to match pace at {{team}}", "{{team}} teammate comparison – {{driver1}} ahead of {{driver2}} once again", "{{driver1}} gaining the edge over {{driver2}} within {{team}}", "{{driver1}} continues to outpace {{driver2}} at {{team}} with consistent results", "{{driver1}} vs {{driver2}} – momentum clearly shifting inside {{team}}", "Inside {{team}} – {{driver1}} asserting dominance over {{driver2}}", "{{driver1}} sets the pace while {{driver2}} plays catch-up at {{team}}", "{{driver1}} extends advantage over {{driver2}} in the {{adjective}} GP weekend", "Confidence growing for {{driver1}} as {{driver2}} seeks answers at {{team}}", "{{driver1}} delivers again for {{team}} while {{driver2}} struggles with balance", "Improved pace from {{driver1}} puts pressure on teammate {{driver2}}", "{{driver1}} emerging as {{team}}’s clear leader after latest results", "{{driver2}} left searching for answers as {{driver1}} raises the bar at {{team}}", "Another strong outing for {{driver1}} keeps them ahead of {{driver2}} in {{team}} battle", "{{team}} management impressed by {{driver1}}’s form compared to {{driver2}}", "{{driver1}} building momentum as {{driver2}} endures a tough run at {{team}}", "{{driver1}}’s consistency giving them the upper hand at {{team}}", "The tide turns inside {{team}} – {{driver1}} now firmly ahead of {{driver2}}", "{{driver1}}’s performance sparks talk within {{team}} about teammate balance", "Steady progress – {{driver1}} keeping {{driver2}} under pressure at {{team}}", "{{driver1}} continues to prove their worth at {{team}} while {{driver2}} trails behind" ] }, { "id": "season_review_mid", "new_type": 14, "titles": [ "{{driver1}} sets the benchmark in the championship battle", "Halfway there – {{driver1}} leads the way as the season unfolds", "The title fight intensifies as {{driver1}} and {{driver2}} trade blows", "{{driver1}} strengthens championship bid with consistent form", "{{driver1}} holds the upper hand over {{driver2}} heading into the break", "Momentum with {{driver1}} as rivals struggle to keep up", "All eyes on {{driver1}} after impressive first half of the season", "Pressure building on {{driver2}} as {{driver1}} extends their lead", "Championship picture shifting with {{driver1}} in control", "Strong first half for {{driver1}} sets the tone for title push", "{{driver1}} emerges as clear favourite after dominant mid-season run", "The battle continues – {{driver1}} and {{driver2}} locked in close fight", "{{driver1}} takes charge of the standings heading into summer break", "Confidence growing for {{driver1}} as momentum carries through the grid", "Mid-season reflections – {{driver1}} establishing themselves as the driver to beat", "Championship tension builds as {{driver1}} keeps {{driver2}} under pressure", "Momentum shifts – {{driver1}} asserting control in the standings", "Halfway through – {{driver1}} looking unstoppable in title race", "Strong mid-season form puts {{driver1}} ahead in the title chase", "Pressure rises for {{driver2}} as {{driver1}} continues dominant streak", "Consistency pays off – {{driver1}} on top after mid-season surge", "At the halfway mark – {{driver1}} sets the pace for championship glory", "{{driver1}} tightening grip on the championship after latest results", "The road to the title – {{driver1}} continues to deliver when it matters" ] }, { "id": "season_review_end", "new_type": 15, "titles": [ "Winners and losers of the {{season_year}} Formula 1 season", "Who impressed and who struggled in {{season_year}}", "Season in review – standout drivers and surprises of {{season_year}}", "Reflecting on {{season_year}} – the highs, lows and turning points", "{{driver1}} crowned champion, but who else shone in {{season_year}}", "From dominance to disaster – the full story of the {{season_year}} season", "The good, the bad and the surprising from {{season_year}} Formula 1", "Our verdict – who defined the {{season_year}} F1 season", "Reviewing a dramatic year – standout performances from {{season_year}}", "A year to remember – key stories that shaped the {{season_year}} championship", "Season analysis – winners, surprises and disappointments of {{season_year}}", "From champions to underdogs – the defining moments of {{season_year}}", "End of season report – how every team and driver fared in {{season_year}}", "The title fight, the surprises and the heartbreaks of {{season_year}}", "Comprehensive review – the ups and downs of the {{season_year}} campaign", "A season of twists – what made {{season_year}} truly unforgettable", "Full recap – everything that mattered in the {{season_year}} Formula 1 season", "Looking back – {{season_year}} delivered excitement and shock in equal measure", "The season comes to a close – analysing who exceeded expectations in {{season_year}}", "Big takeaways from {{season_year}} – the stories behind the results", "Season finale reflections – what we learned from {{season_year}}", "Closing chapter – key lessons from an unpredictable {{season_year}}", "Final thoughts – {{season_year}} gave us triumph, drama and redemption", "After a thrilling year, {{season_year}} leaves behind unforgettable storylines", "The curtain falls on {{season_year}} – who came out on top and who fell short" ] }, { "id": "driver_reactions_post_race", "new_type": 16, "titles": [ "Driver reactions after the {{adjective}} Grand Prix weekend", "Voices from the paddock – emotions run high after the {{adjective}} GP", "Hear what the grid had to say after the {{country}} Grand Prix", "Post-race roundup – reactions from the {{circuit}} paddock", "Inside the paddock – driver comments after {{country}} GP drama", "Post-race emotions overflow after the {{adjective}} GP at {{circuit}}", "The {{country}} GP in words – joy, frustration and everything in between", "Drivers share their thoughts after a wild weekend in {{country}}", "What the drivers said after an intense battle at {{circuit}}", "The paddock reacts to the {{adjective}} GP – contrasting emotions across the field", "Driver quotes after the {{country}} Grand Prix – highs and lows everywhere", "All the post-race quotes from the {{adjective}} GP at {{circuit}}", "From joy to despair – driver reactions after the {{country}} GP", "Press room roundup – what the drivers had to say after {{adjective}} GP Sunday", "After the flag – drivers reflect on a challenging {{country}} Grand Prix", "Hot takes from {{country}} – how drivers summed up the {{adjective}} GP", "Emotions boil over after {{country}} GP – full driver reactions", "Post-race microphones catch every emotion after {{adjective}} GP", "Who smiled and who frowned after the {{country}} GP?", "{{happy_driver}}: \"That was one of my best races ever\"", "{{happy_driver}}: \"We executed perfectly – I’m really proud\"", "{{happy_driver}} delighted after strong {{country}} GP result", "{{happy_driver}} full of praise for {{happy_team}} after {{adjective}} GP success", "{{happy_driver}}: \"We nailed it today, this is what we’ve been working for\"", "{{happy_driver}} reflects on ‘incredible’ {{adjective}} Grand Prix at {{circuit}}", "{{happy_driver}} thanks the team after emotional {{country}} GP", "{{happy_driver}} upbeat after solid performance at {{circuit}}", "{{happy_driver}} hails team’s effort following {{adjective}} GP heroics", "{{happy_driver}} confident momentum will continue after {{country}} GP", "Smiles in the {{happy_team}} garage after strong {{adjective}} GP result", "{{happy_driver}}: \"We’ve proven our potential today\"", "{{happy_driver}}: \"The car was amazing – huge credit to {{happy_team}}\"", "{{happy_driver}} thrilled with progress after tough {{adjective}} GP", "{{happy_driver}}: \"It feels so good to be back at the front\"", "Positive vibes in {{happy_team}} camp after encouraging {{country}} GP", "{{unhappy_driver}}: \"We should’ve done much better today\"", "{{unhappy_driver}} frustrated after chaotic {{country}} Grand Prix", "{{unhappy_driver}}: \"Strategy calls didn’t go our way\"", "{{unhappy_driver}} left disappointed after missed opportunity in {{adjective}} GP", "{{unhappy_driver}}: \"The pace was there, but luck wasn’t\"", "{{unhappy_driver}} vents frustration after difficult {{circuit}} weekend", "{{unhappy_driver}} admits disappointment after underwhelming {{country}} GP", "{{unhappy_driver}}: \"We threw away good points today\"", "{{unhappy_driver}} critical after team error at {{circuit}}", "{{unhappy_driver}} calls for answers after frustrating {{country}} GP", "Frustration for {{unhappy_driver}} as {{unhappy_team}} miss out on key result", "{{unhappy_driver}}: \"We had the car to fight, but it didn’t come together\"", "{{unhappy_driver}} remains optimistic despite {{adjective}} GP struggles", "{{happy_driver}} and {{unhappy_driver}} give contrasting reactions after {{country}} GP", "{{happy_driver}} upbeat, {{unhappy_driver}} dejected after {{country}} GP", "Post-race reflections – joy for {{happy_driver}}, frustration for {{unhappy_driver}}", "Paddock emotions split after {{adjective}} GP at {{circuit}}", "Drivers speak out – highs and lows from {{country}} Grand Prix", "Relief for some, regret for others after {{adjective}} GP weekend", "After the chequered flag – how the drivers reacted in {{country}}", "Reactions from {{circuit}} – elation, frustration and everything in between", "Drivers reflect on a rollercoaster {{adjective}} GP weekend", "Post-race press quotes – how the grid reacted to {{country}} GP", "What did the drivers think? Full reactions after {{adjective}} GP", "How did the drivers feel about their {{country}} GP performances?", "{{unhappy_driver}} hinting possible exit from {{unhappy_team}}?", "The {{adjective}} GP aftermath – honest words from the drivers", "Paddock chatter – the best driver quotes from {{circuit}}", "After the chaos – drivers give their takes on {{country}} Grand Prix", "From joy to disappointment – {{country}} GP reaction roundup", "Straight from the drivers – post-race words from {{circuit}}", "Post-race press buzz – top quotes from {{adjective}} GP", "Hear it from the drivers – full reaction to {{country}} GP", "Raw emotions on display after {{adjective}} GP at {{circuit}}", "Behind the microphones – everything drivers said after {{country}} GP", "Words from the paddock – weekend reactions from {{country}}", "The weekend in words – drivers react to {{adjective}} Grand Prix", "Top quotes from {{circuit}} – a mix of smiles and frustration", "Paddock roundup – what everyone’s saying after {{country}} GP", "The best post-race comments from {{circuit}}", "Driver reactions flood in after {{adjective}} Grand Prix weekend", "What they said – all the driver quotes from {{country}} GP", "Every emotion in the book – {{adjective}} GP reaction roundup", "Reactions, emotions, and lessons learned from {{country}} GP", "Voices of the weekend – the drivers react to {{adjective}} GP", "The paddock speaks – all the post-race emotions from {{circuit}}", "Post-race press conference recap from {{country}} GP", "The {{country}} Grand Prix aftermath – from elation to disappointment", "How the drivers summed up their {{adjective}} GP experiences" ] }, { "id": "massive_exit", "new_type": 17, "titles": [ "SHOCKING – {{driver1}} to leave {{team1}} at the end of the season", "Breaking news – {{driver1}} set to exit {{team1}} after {{season_year}}", "Major announcement – {{driver1}} parts ways with {{team1}}", "{{driver1}} confirms departure from {{team1}} after {{season_year}} campaign", "Surprise exit – {{driver1}} to leave {{team1}} in a stunning move", "{{driver1}} and {{team1}} to go separate ways after {{season_year}}", "BREAKING – {{driver1}} ends tenure with {{team1}} at season’s end", "{{driver1}} announces exit from {{team1}}, leaving fans stunned", "End of an era – {{driver1}} to depart {{team1}} after {{season_year}}", "It's done – {{driver1}} set to leave {{team1}}", "{{driver1}} to bid farewell to {{team1}} after the conclusion of {{season_year}}", "Unexpected move – {{driver1}} parts ways with {{team1}}", "{{driver1}} confirms exit from {{team1}}, sparking speculation about future", "Breaking – {{driver1}} to leave {{team1}}, ending a significant chapter", "{{driver1}} set to exit {{team1}}, leaving fans and pundits surprised", "Major shake-up – {{driver1}} to depart from {{team1}} after {{season_year}}", "'It just hasn't worked out' – {{driver1}} to leave {{team1}}", "OFFICIAL - {{driver1}} to leave {{team1}} at the end of the season", "{{driver1}} and {{team1}} will part ways at the end of the season, {{team1}} confirms", "'We're grateful for his time here' – {{team1}} announces {{driver1}}'s exit", "{{driver1}} will leave {{team1}} at the end of the season, {{team1}} confirms" ] }, { "id": "massive_signing", "new_type": 18, "titles": [ "BREAKING – {{driver1}} signs with {{team2}} for the upcoming season", "Major signing – {{driver1}} joins {{team2}} in a stunning move", "{{driver1}} confirmed as new driver for {{team2}}", "SHOCKING – {{driver1}} to race for {{team2}} next season", "HERE WE GO - {{driver1}} makes the switch to {{team2}}", "'We're very excited to have him' – {{team2}} announces signing of {{driver1}}", "{{driver1}} to join {{team2}} after signing multi-year deal", "OFFICIAL - {{driver1}} signs with {{team2}} for the next season", "Huge move – {{driver1}} officially joins {{team2}} for next season", "Blockbuster transfer – {{driver1}} signs with {{team2}}", "Confirmed – {{driver1}} becomes new {{team2}} driver for the upcoming season", "Big announcement – {{team2}} secures the signing of {{driver1}}", "It’s done – {{driver1}} to race with {{team2}} from next year", "Massive shake-up – {{driver1}} officially moves to {{team2}}", "{{driver1}} completes major switch to {{team2}}", "{{team2}} lands top talent – {{driver1}} confirmed for next season", "Official statement – {{driver1}} joins {{team2}} on a long-term deal", "{{team2}} announces the signing of {{driver1}} in major transfer news", "Big news – {{driver1}} set to partner with {{team2}} from next year", "Transfer bombshell – {{driver1}} officially heading to {{team2}}", "{{driver1}} signs with {{team2}} in one of the biggest moves of the season", "Statement move – {{team2}} secures star driver {{driver1}}", "'I am looking forward to this new challenge' – {{driver1}} joins {{team2}}", "{{driver1}} makes sensational switch to {{team2}} for the upcoming season" ] }, { "id": "next_season_grid", "new_type": 19, "titles": [ "The {{season_year}} F1 grid – all confirmed driver line-ups", "Looking ahead to {{season_year}} – complete driver roster revealed", "Next season’s grid – who’s where in {{season_year}} Formula 1", "{{season_year}} driver line-ups confirmed – full team-by-team guide", "The future is set – {{season_year}} F1 driver line-ups announced", "Breaking down the {{season_year}} Formula 1 grid – who’s driving for whom", "Complete guide to the {{season_year}} F1 driver roster", "What to expect in {{season_year}} – full driver line-ups revealed", "Team-by-team look at the {{season_year}} Formula 1 grid", "{{season_year}} F1 driver line-ups – all you need to know" ] }, { "id": "feeder_series_review", "new_type": 20, "titles": [ "How did the F2 and F3 seasons unfold in {{season_year}}?", "Reviewing the {{season_year}} F2 and F3 championships – {{f2_champion}} and {{f3_champion}} crowned", "Standout moments from the {{season_year}} F2 and F3 seasons", "{{f2_champion}} and {{f3_champion}} take the crowns – a look back at {{season_year}}", "The rise of future stars – highlights from {{season_year}} F2 and F3", "'We knew he had something special' – reflecting on {{f2_champion}}'s {{season_year}} F2 title", "From the juniors to the spotlight – {{season_year}} F2 and F3 season review", "Key takeaways from the {{season_year}} F2 and F3 championships", "How did {{f2_champion}} and {{f3_champion}} clinch their titles in {{season_year}}?", "{{f2_champion}} and {{f3_champion}} crowned – a look back at the {{season_year}} feeder series seasons", "Rising stars of {{season_year}} – reviewing the F2 and F3 championships", "The journey to F1 – highlights from the {{season_year}} F2 and F3 seasons", "Season review of the {{season_year}} F2 and F3 championships", "From contenders to champions – how {{f2_champion}} and {{f3_champion}} won in {{season_year}}" ] } ] ================================================ FILE: src/data/news/turning_points_prompts_templates.json ================================================ [ { "id": "technical_directive", "new_type": 100, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the FIA is considering introducing a new technical directive that would impact the {{component}} designs already this season. The objective of the directive is to {{reason}}. Include quotes from the FIA technical delegate and make up some change in the said {{component}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the FIA has announced a new technical directive that will impact the {{component}} designs already this season. The objective of the directive is to {{reason}}. Include quotes from the FIA technical delegate explaining the changes and their expected impact on performance. Paddock says that {{best_team}} seems to be the most benefited from the change, while {{second_best}} is close. {{worse_team}} appears to be struggling with the new techniocal directive, the same as {{second_worse}}. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the FIA has decided not to implement a proposed technical directive that would have impacted the {{component}} designs already this season. The objective of the directive is to {{reason}}. Include quotes from the FIA technical delegate explaining the decision and expressing confidence in the current regulations and the quality of racing it brings. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long." }, { "id": "mid_season_transfer", "new_type": 101, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the team {{team}} is looking to sign {{driver_in}} from {{driver_in_team}} during the season to replace {{driver_out}}. {{driver_substitute_part}} Include quotes from the team principal and the incoming driver teasing it a bit, as it can't be confirmed yet. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the team {{team}} has announced that {{driver_in}} will join them from {{driver_in_team}} during the season to replace {{driver_out}}. {{driver_substitute_part}} Include quotes from the team principal and the incoming driver expressing excitement about this new chapter. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the team {{team}} has decided not to pursue the signing of {{driver_in}} from {{driver_in_team}} during the season to replace {{driver_out}}. {{driver_substitute_part}} Include quotes from the team principal explaining the decision and expressing confidence in their current driver lineup. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long." }, { "id": "investment_takeover", "new_type": 102, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about rumors suggesting that investors from {{country}} are preparing a potential {{amount}} million investment in {{team}}, possibly acquiring around {{share}}% of the team. Describe how this has stirred discussions within the paddock and how it could reshape the team's financial future. Include quotes from insiders or unnamed team sources offering cautious comments, emphasizing that nothing has been confirmed yet. Use clear, natural {{language}} in a professional journalistic tone, similar to Autosport or Motorsport.com. Avoid slang, exaggeration, or unnecessary drama. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} confirming that {{team}} has officially secured a {{amount}} million investment deal with a {{country}} consortium, giving them a {{share}}% stake in the team. Describe how this new funding will strengthen {{team}}’s long-term ambitions and stability, and what the deal means for their competitiveness in Formula 1. Include quotes from the team principal and a representative of the {{country}} investors expressing optimism about the partnership. Use clear, professional {{language}} in the tone of Autosport or Motorsport.com. Avoid exaggeration or unnecessary flair. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how {{team}} has denied reports of an impending {{amount}} million investment or takeover bid from {{country}} investors, despite widespread speculation. Explain how the rumors emerged, why the team decided to clarify the situation, and how this reflects on their current financial independence. Include quotes from the team principal dismissing the claims and reaffirming the team's direction. Use clear, natural {{language}} in a professional journalistic tone similar to Autosport or Motorsport.com. Avoid slang, exaggeration, or sensational language. The article should be between 400 and 500 words long." }, { "id": "dsq", "new_type": 103, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the team {{team}} may get both cars disqualified from the {{adjective}} Grand Prix due to a technical infringement found during post-race inspection related to their {{component}}. {{driver_1}} finishing position was P{{driver_1_pos}} and got {{driver_1_points}} points, while {{driver_2}} finished in P{{driver_2_pos}} and earned {{driver_2_points}} points. Include quotes from their team principal expressing confidence in their innocence. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the team {{team}} has had both cars disqualified from the {{adjective}} Grand Prix due to a technical infringement found during post-race inspection related to their {{component}}. {{driver_1}} finishing position was P{{driver_1_pos}} and got {{driver_1_points}} points before the disqualification, while {{driver_2}} finished in P{{driver_2_pos}} and earned {{driver_2_points}} points that he then got stripped away of. Include quotes from their team principal expressing confidence in their innocence. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the team {{team}} has finally avoided disqualification for their cars at the {{adjective}} Grand Prix after a technical infringement found during post-race inspection related to their {{component}} was overturned on appeal. Include quotes from their team principal expressing relief and satisfaction with the outcome. Use clear, natural {{language}} in a professional journalistic style, similar to outlets like Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long." }, { "id": "race_substitution", "new_type": 105, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the {{original_race}} Grand Prix is reportedly at risk of cancellation due to {{reason}}. Mention that the FIA and Formula 1 are evaluating possible replacements, with {{substitute_race}} GP emerging as one of the leading candidates to take its place on the calendar. Include background on the situation, quotes from F1 officials or local organizers expressing caution or optimism, and context about how such changes impact the championship. Use clear, professional language similar to Autosport or Motorsport.com. Avoid sensationalism or unnecessary adjectives. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the {{original_race}} Grand Prix has officially been cancelled due to {{reason}}, and that {{substitute_race}} GP will take its place on the Formula 1 calendar. Describe how the decision was made, include official statements from the FIA and Formula 1 Management confirming the change, and mention the reaction from fans and teams. Provide background on {{substitute_race}} returning or joining the calendar and how this affects travel logistics and the season's balance. Use clear, professional {{language}} in a realistic news tone similar to Autosport or Motorsport.com. Avoid sensationalism, exaggeration, or flowery language. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that the {{original_race}} Grand Prix will go ahead as originally planned after weeks of uncertainty caused by {{reason}}. Mention that concerns about a potential cancellation or replacement have been resolved following confirmation from the FIA and local organizers. Include quotes from key officials expressing relief and confidence in the event’s preparation, and describe how the confirmation stabilizes the remainder of the calendar. Use clear, professional {{language}} in a journalistic tone similar to Autosport or Motorsport.com. Avoid exaggeration or unnecessary drama. The article should be between 400 and 500 words long." }, { "id": "driver_injury", "new_type": 106, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} explaining that {{driver}} from {{team}} is dealing with {{condition}} caused by {{reason}}. Mention that the issue could force the driver to miss {{races_affected_count}} Grand Prix events, depending on recovery progress. The expected comeback race would be the {{expectedReturnCountry}} GP. Describe how the situation developed, what the team has said publicly. {{reserve_driver_part}} Include brief medical context about the condition and how it can affect a Formula 1 driver’s performance or comfort in the cockpit. Use clear, factual, professional {{language}} in a tone similar to Autosport or Motorsport.com. Avoid sensationalism or exaggeration. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} confirming that {{driver}} from {{team}} will indeed miss the upcoming {{races_affected_count}} Grand Prix events due to {{condition}} following {{reason}}. {{reserve_driver_part}} {{driver}} is expected around the {{expectedReturnCountry}} Grand Prix. Include quotes from team officials about prioritizing the driver’s recovery, reactions from the paddock, and context on how this absence may affect {{team}}’s performance in the championship. Maintain a professional, journalistic tone similar to Autosport or Motorsport.com, avoiding exaggeration or dramatic phrasing. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} confirming that {{driver}} from {{team}} will take part in the upcoming Grand Prix despite recent concerns about {{condition}} caused by {{reason}}. Mention that after medical checks and physiotherapy, the driver has been cleared to race, ending speculation about a potential absence. Include comments from {{driver}} or team representatives expressing relief and determination to continue competing. {{reserve_driver_part}} Emphasize that the team will monitor {{driver}}’s condition closely over the next races. Use clear, professional, and realistic {{language}} in a tone similar to Autosport or Motorsport.com. Avoid unnecessary drama or exaggeration. The article should be between 400 and 500 words long." }, { "id": "turning_point_engine_regulation", "new_type": 107, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the FIA is considering a {{type}} change to engine regulations for next season, with a focus on {{change_area}}. Explain how the discussion emerged, why this area is under review, and what it could mean for manufacturers. Include quotes from FIA officials about maintaining fairness and performance balance. Paddock opinion suggests that {{winner_names}} could benefit, while {{loser_names}} may be hindered if the changes are approved. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} confirming that the FIA has approved a {{type}} change to engine regulations for next season, focused on {{change_area}}. Describe the key points of the change and what it means for teams and manufacturers. Include quotes from FIA officials and at least one team principal reacting to the decision. The paddock expects {{winner_names}} to benefit from the new direction, while {{loser_names}} could face a tougher adjustment. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the FIA has decided not to proceed with a proposed {{type}} change to engine regulations focused on {{change_area}}. Explain the reasoning behind the decision and note that the current engine rules will remain in place. Include quotes from FIA officials about stability and cost control. Mention that the paddock had expected {{winner_names}} to benefit and {{loser_names}} to be hindered, but those shifts will not materialize. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long." }, { "id": "young_drivers", "new_type": 108, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the paddock is buzzing about a new wave of young driver prospects led by {{driver1}} and {{driver2}} after standout seasons on the junior ladder. Use the list below for names, ages, series, and standings: {{prospects_list}}. Mention their ages and results, and explain why teams are tracking them as future F1 talent. Include quotes from a team principal and a junior team manager. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "positive_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how {{driver1}} and {{driver2}} have convinced much of the paddock that they are ready for bigger opportunities after strong performances on the junior ladder. Use the list below for names, ages, series, and standings: {{prospects_list}}. Highlight praise from F1 teams and explain what makes their performances stand out. Include quotes from a team principal and a driver mentor. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long.", "negative_prompt": "Write ONLY the body (not the title) of a news article in {{language}} about how the hype around {{driver1}} and {{driver2}} has been tempered despite strong junior results. Use the list below for names, ages, series, and standings: {{prospects_list}}. Explain why some teams believe they need more seasoning before stepping up. Include quotes from a team principal and a junior series observer. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 400 and 500 words long." }, { "id": "aduo_engine", "new_type": 109, "prompt": "Write ONLY the body (not the title) of a news article in {{language}} about the FIA opening an ADUO (Additional Development and Upgrade Opportunities) window during the {{number_period}} quarter of the season. ADUO is a system created by the FIA to help less powerful engine manufacturers improve through the season by allowing targeted upgrades within strict cost and fairness controls. Explain why the system exists, how the window works in broad terms, and what the paddock expects from it. The manufacturers expected to take advantage of this window are: {{manufacturers}}. Below is a qualitative summary of the expected changes (do not use numbers or percentages):\n\n{{upgrade_summary}}\n\nInclude quotes from an FIA spokesperson and at least one team principal or engine representative reacting to the window. Use clear, natural {{language}} in a professional journalistic style similar to Autosport or Motorsport.com. Avoid slang, overly dramatic phrasing, or unnecessary flourishes. The article should be between 500 and 650 words long." } ] ================================================ FILE: src/data/news/turning_points_titles_templates.json ================================================ [ { "id": "fia_directive", "new_type": 100, "turning_titles": [ "FIA reportedly drafting new technical directive on {{component}}", "Rumors suggest FIA preparing a rule adjustment concerning {{component}}", "Sources indicate FIA is considering action over {{component}} performance", "Potential FIA intervention expected regarding {{component}} regulations", "Whispers in the paddock - FIA to address {{component}} issues soon", "A new technical directive on {{component}} could be imminent, say insiders", "FIA looking into possible changes for {{component}} rules", "Teams brace for potential FIA directive on {{component}} performance", "Earthquake in the paddock? FIA thinks about new regulations on {{component}}", "'It is a possibility' – FIA on upcoming {{component}} technical directive", "Shake-up ahead? FIA technical directive on {{component}} rumored to be in the works" ], "positive_titles": [ "FIA enforces new directive affecting {{component}} performance across the grid", "Official - FIA technical directive on {{component}} now in effect", "Teams react as FIA implements new {{component}} rules", "FIA confirms new regulation changes regarding {{component}}", "FIA's new {{component}} directive shakes up the competition", "New FIA rules on {{component}} expected to impact car performance", "FIA's {{component}} directive sparks debate among teams", "FIA's latest technical directive on {{component}} set to change the landscape", "'We have to adapt quickly' – Teams respond to FIA's {{component}} directive", "'It wont change much' – FIA downplays impact of new {{component}} directive" ], "negative_titles": [ "FIA shelves planned directive on {{component}} after internal review", "Rumored {{component}} directive abandoned following team objections", "No changes to {{component}} regulations after FIA evaluation", "FIA denies reports of imminent {{component}} technical directive", "FIA decides against new {{component}} rules after consultation", "Relief among teams as FIA drops {{component}} directive plans", "FIA opts not to pursue new {{component}} regulations", "FIA confirms no changes to {{component}} rules at this time", "'We found no need for changes' – FIA on scrapped {{component}} directive", "'It was never going to happen' – FIA on rumors of {{component}} technical directive" ] }, { "id": "mid_season_transfer", "new_type": 101, "turning_titles": [ "{{team}} reportedly considering replacing {{driver_out}} with {{driver_in}} as soon as possible", "Internal tension at {{team}} as rumors link {{driver_in}} to {{driver_out}}’s seat", "Sources suggest {{team}} evaluating a possible swap involving {{driver_out}} and {{driver_in}}", "Speculation grows around {{team}}’s lineup after disappointing results from {{driver_out}}", "Whispers in the paddock - {{driver_in}} could be set to replace {{driver_out}} at {{team}}", "Rumors of a mid-season driver change at {{team}} involving {{driver_out}} and {{driver_in}}", "{{team}} is thinking about releasing {{driver_out}} – could {{driver_in}} be the replacement?", "Insiders hint at potential driver shuffle at {{team}} with {{driver_in}} eyeing {{driver_out}}’s position", "'We think about every option' – {{team}} on possible driver change" ], "positive_titles": [ "It’s official - {{driver_in}} replaces {{driver_out}} at {{team}} for the remainder of the season", "{{team}} confirms driver change – {{driver_out}} out, {{driver_in}} in", "{{driver_in}} steps in for {{driver_out}} as {{team}} reshuffles lineup", "Confirmed - {{driver_in}} joins {{team}} for the next race, replacing {{driver_out}}", "{{team}} makes bold move, swaps {{driver_out}} for {{driver_in}}", "{{team}} shakes up lineup with a surprise move involving {{driver_in}}", "HERE WE GO - {{driver_in}} takes over {{driver_out}}’s seat at {{team}}", "{{team}} announces mid-season driver change, {{driver_in}} to replace {{driver_out}}", "'I'm excited for the challenge' – {{driver_in}} on joining {{team}} for the next race" ], "negative_titles": [ "{{team}} denies rumors of replacing {{driver_out}} as soon as possible", "No changes at {{team}} as management reaffirms trust in {{driver_out}}", "Rumors dismissed - {{driver_out}} keeps seat at {{team}} despite speculation", "{{team}} closes talks of a driver change during the season", "{{team}} stands by {{driver_out}}, quashes replacement rumors", "Relief for {{driver_out}} as {{team}} confirms no driver changes", "{{team}} rebuffs speculation, {{driver_out}} remains in the lineup", "{{team}} puts an end to the silly season driver change rumors, {{driver_out}} safe", "{{team}} reaffirms commitment to {{driver_out}}, denies replacement talks", "'I never felt threatened' – {{driver_out}} on rumors of being replaced at {{team}}" ] }, { "id": "investment_takeover", "new_type": 102, "turning_titles": [ "Reports hint at {{amount}}M {{country}} investment in {{team}}", "{{team}} in talks with {{country}} investors for {{share}}% stake", "Rumors grow of {{amount}}M {{country}} deal for {{team}}", "{{team}} draws {{country}} interest worth about {{amount}}M", "Whispers - {{team}} set for {{country}} funding near {{amount}}M", "Insiders point to {{country}} group eyeing {{share}}% of {{team}}", "Could {{team}} face a {{share}}% takeover bid from {{country}}?", "Speculation over {{country}}-{{team}} deal valued at {{amount}}M" ], "positive_titles": [ "Confirmed - {{team}} lands {{amount}}M {{country}} deal ({{share}}%)", "{{team}} finalizes {{amount}}M {{country}} investment boost", "{{team}} partners with {{country}} investors ({{share}}% stake)", "Huge boost as {{country}} group buys {{share}}% of {{team}}", "{{team}} announces {{amount}}M {{country}} investment", "{{team}} welcomes {{country}} investors ({{share}}%)", "{{team}} secures {{amount}}M {{country}} funding", "BREAKING - {{team}} confirms {{country}} {{amount}}M deal", "{{team}} celebrates {{country}} partnership worth {{amount}}M" ], "negative_titles": [ "{{team}} denies {{amount}}M {{country}} talks", "No {{amount}}M {{country}} deal for {{team}}", "{{team}} and {{country}} group fail to reach deal", "{{team}}–{{country}} investment collapsed after {{share}}% talks", "{{team}} ends {{country}} takeover speculation ({{share}}%)", "{{team}} {{country}} talks fall through – {{amount}}M off table", "{{team}} rebuffs {{country}} investment rumors", "No {{share}}% deal between {{team}} and {{country}} group", "'We are not for sale' – {{team}} shuts down {{country}} rumors" ] }, { "id": "turning_point_dsq", "new_type": 103, "turning_titles": [ "FIA reportedly investigating {{team}} for irregularities at the {{adjective}} Grand Prix", "Post-race inspection raises questions over {{team}}’s {{component}} compliance", "Sources claim {{team}} could face disqualification from {{circuit}}", "Controversy brewing as {{team}} is under FIA scrutiny after {{adjective}} Grand Prix", "Whispers in the paddock - {{team}} may be in breach of regulations after {{country}}", "Rumors of potential penalties for {{team}} following {{adjective}} GP", "Insiders hint at possible disqualification for {{team}} after {{adjective}} Grand Prix race", "{{team}} faces FIA probe over {{component}} legality after {{adjective}} GP inspection", "'At the moment we are gathering information' – FIA on {{team}} investigation" ], "positive_titles": [ "FIA confirms {{team}} disqualified from {{adjective}} Grand Prix following irregularities", "Both {{team}} cars disqualified from {{adjective}} GP – FIA releases statement", "Major blow for {{team}} as post-race penalties confirmed at {{adjective}} Grand Prix", "{{team}} loses {{adjective}} GP results after FIA investigation outcome on {{component}}", "FIA rules against {{team}}, disqualifies them from {{country}}", "{{team}} stripped of {{adjective}} Grand Prix points after {{component}} breach confirmed", "Disappointment for {{team}} as FIA enforces disqualification from {{circuit}}", "BREAKING - {{team}} disqualified from {{adjective}} GP after FIA review on their {{component}}", "'We don't agree with the decision' – {{team}} responds to {{country}} disqualification" ], "negative_titles": [ "FIA clears {{team}} of wrongdoing after {{adjective}} investigation into {{component}}", "Investigation closed - {{team}} retains {{adjective}} GP results", "{{team}} exonerated as FIA finds no breach after {{circuit}} review of {{component}}", "Rumors dismissed – no penalties issued to {{team}} after {{circuit}}", "Relief for {{team}} as FIA ends probe with no action taken", "{{team}} avoids disqualification, FIA confirms compliance after {{adjective}} GP", "{{team}} retains points after FIA investigation finds no irregularities with their {{component}}", "{{team}} cleared of any violations following {{adjective}} GP", "'I knew we didn't do anything wrong' – {{team}} on FIA investigation outcome" ] }, { "id": "engine_update", "new_type": 104, "turning_titles": [ "{{engine_supplier}} preparing major power unit upgrade for next races", "Reports indicate {{engine_supplier}} developing improved engine spec", "Rumors of significant {{engine_supplier}} update spark excitement in the paddock", "Insiders hint at performance boost from upcoming {{engine_supplier}} power unit", "Whispers in the paddock - {{engine_supplier}} set to unveil new engine update", "Sources suggest {{engine_supplier}} working on competitive engine upgrade", "Could {{engine_supplier}}’s new engine spec be a game-changer?", "Speculation grows over potential performance leap from {{engine_supplier}} update", "'I think it's a step in the right direction' – {{engine_supplier}} on upcoming upgrade" ], "positive_titles": [ "Success for {{engine_supplier}} as new power unit delivers strong results", "{{engine_supplier}}’s latest engine spec praised for improved performance", "Positive outcome – {{engine_supplier}} upgrade boosts customer teams", "New {{engine_supplier}} engine update proves decisive improvement", "{{engine_supplier}} engine upgrade impresses teams with performance gains", "Teams laud {{engine_supplier}} for effective power unit update", "BREAKING - {{engine_supplier}} engine update shows promising results", "'We are very happy with the progress' – {{engine_supplier}} on new engine spec" ], "negative_titles": [ "No improvement seen after {{engine_supplier}} engine update", "{{engine_supplier}} engine upgrade fails to deliver expected gains", "Disappointment as {{engine_supplier}} update shows no performance increase", "{{engine_supplier}} engine spec remains unchanged after recent update", "Relief for rival teams as {{engine_supplier}} upgrade falls short", "{{engine_supplier}} engine update does not meet performance expectations", "{{engine_supplier}} confirms no significant changes after latest upgrade", "'It's not the step we hoped for' – {{engine_supplier}} on engine update results" ] }, { "id": "race_substitution", "new_type": 105, "turning_titles": [ "{{original_race}} Grand Prix at risk of cancellation due to {{reason}}", "Reports suggest {{original_race}} GP may be called off amid {{reason}} concerns", "Sources indicate potential cancellation of {{original_race}} Grand Prix", "FIA considering alternatives as {{original_race}} GP faces {{reason}} issues", "Could {{substitute_race}} replace {{original_race}} GP this season?", "Speculation grows over possible cancellation of {{original_race}} Grand Prix", "'It doesn't look good' – insiders on {{original_race}} GP status amid {{reason}}", "'We're exploring options' – FIA on potential changes to {{original_race}} GP", "{{original_race}} GP future uncertain as organizers struggle with {{reason}}", "Calendar shake-up likely - {{original_race}} GP in doubt over {{reason}}", "Backup venues being evaluated after concerns surrounding {{original_race}} GP", "{{substitute_race}} emerges as leading candidate to replace {{original_race}} Grand Prix", "Talks intensify between FOM and {{substitute_race}} officials amid {{original_race}} GP uncertainty", "Calendar rumors suggest {{original_race}} GP may be swapped for {{substitute_race}} due to {{reason}}", "Officials admit contingency discussions underway for {{original_race}} GP amid {{reason}} concerns" ], "positive_titles": [ "Confirmed - {{original_race}} Grand Prix cancelled, {{substitute_race}} to take its place", "{{substitute_race}} officially announced as replacement for {{original_race}} GP", "Calendar update - {{substitute_race}} joins F1 schedule after {{original_race}} GP cancellation", "FIA confirms {{substitute_race}} will step in following {{original_race}} Grand Prix cancellation", "{{substitute_race}} steps up as replacement event after {{original_race}} GP called off", "Formula 1 confirms {{substitute_race}} as official substitute for {{original_race}} Grand Prix", "Breaking - {{original_race}} GP cancelled — {{substitute_race}} fills the gap in this year's calendar", "{{substitute_race}} to make surprise return as {{original_race}} GP replacement", "F1 calendar reshuffled - {{substitute_race}} replaces {{original_race}} Grand Prix", "Official statement - {{original_race}} GP dropped, {{substitute_race}} joins the lineup", "'We're excited to host F1 again' – {{substitute_race}} organizers on replacing {{original_race}} GP", "FIA and FOM confirm {{substitute_race}} as new venue after {{original_race}} cancellation" ], "negative_titles": [ "FIA confirms {{original_race}} Grand Prix will go ahead as planned despite {{reason}} concerns", "{{original_race}} GP officially back on as issues surrounding {{reason}} resolved", "After weeks of uncertainty, {{original_race}} Grand Prix confirmed to proceed as scheduled", "Rumors dismissed - {{original_race}} GP to take place as originally planned", "FIA statement ends speculation – {{original_race}} Grand Prix safe for this season", "Organizers confirm {{original_race}} GP will happen after resolving {{reason}} complications", "False alarm - {{original_race}} GP remains on the calendar despite earlier doubts", "{{original_race}} organizers secure final approval, event to go ahead as planned", "{{original_race}} GP cleared to proceed following successful FIA inspection", "All systems go - {{original_race}} Grand Prix confirmed following resolution of {{reason}}", "Confirmed - {{original_race}} Grand Prix to proceed as scheduled", "'At the end of the day, we all wanted to race there' – FIA on {{original_race}} GP confirmation" ] }, { "id": "driver_injury_or_illness", "new_type": 106, "turning_titles": [ "{{driver}} ({{team}}) doubtful for {{next_race}} due to {{condition}}; {{reserve_driver}} on standby", "Concerns grow over {{driver}}'s fitness amid {{condition}} — {{reserve_driver}} ready to step in", "{{driver}} faces {{condition}} ahead of {{next_race}}; {{reserve_driver}} preparing just in case", "Medical checks for {{driver}} after {{condition}}; participation in {{next_race}} uncertain", "{{team}} monitoring {{driver}} closely due to {{condition}}; decision expected soon", "Training setback for {{driver}} — {{condition}} raises doubts over {{next_race}}", "{{reserve_driver}} could replace {{driver}} as {{condition}} affects race prep", "Rumors in the paddock - {{driver}} struggling with {{condition}}, {{reserve_driver}} may step in", "Sources - {{driver}} dealing with {{condition}}; {{next_race}} start not guaranteed", "Fitness concerns at {{team}} — {{driver}} under observation for {{condition}}", "{{driver}} limited by {{condition}} during prep; {{reserve_driver}} on alert", "Question mark for {{driver}} ahead of {{next_race}} due to {{condition}}", "{{team}} weighing options as {{driver}} recovers from {{condition}}", "Could {{driver}} miss {{races_affected_count}} race(s)? {{condition}} putting timeline at risk", "Setback for {{team}} - {{driver}} coping with {{condition}} ahead of {{next_race}}" ], "positive_titles": [ "Confirmed - {{driver}} sidelined with {{condition}}; {{reserve_driver}} to race until {{expected_return}}", "{{team}} statement - {{driver}} to miss {{races_affected_count}} race(s) due to {{condition}}; {{reserve_driver}} steps in", "{{driver}} officially ruled out of {{next_race}} after {{condition}}; {{reserve_driver}} to replace him", "It’s official — {{driver}} out with {{condition}}, {{reserve_driver}} takes over for {{team}}", "Timeline set - {{driver}} targeting {{expected_return}} return after {{condition}}; {{reserve_driver}} fills the gap", "‘Health comes first’ - {{team}} confirms absence for {{driver}} due to {{condition}}", "{{reserve_driver}} to make appearance as {{driver}} recovers from {{condition}}", "{{driver}} to skip upcoming events — recovery from {{condition}} ongoing", "Confirmed - {{driver}} to miss {{races_affected_count}} race(s) — {{condition}} behind the decision", "Doctors advise rest - {{driver}} sidelined until {{expected_return}} due to {{condition}}", "{{team}} - ‘We’ll support {{driver}} through recovery’; {{reserve_driver}} takes his place at {{next_race}}" ], "negative_titles": [ "Cleared to race - {{driver}} fit for {{next_race}} despite {{condition}} scare", "Good news for {{team}} — {{driver}} passed medical checks after {{condition}}", "Speculation ends - {{driver}} will race at {{next_race}} despite {{condition}} concerns", "All clear — {{driver}} given the green light following {{condition}} recovery", "{{driver}} confident - ‘I’m ready to race’ after dealing with {{condition}}", "No layoff needed - {{driver}} completes treatment and will start {{next_race}}", "False alarm - {{driver}} available despite earlier {{condition}} reports", "Fitness confirmed - {{driver}} back to full form after {{condition}}; {{reserve_driver}} stands down", "{{driver}} resumes normal training after {{condition}} clearance", "Update - {{driver}} avoids time on the sidelines following {{condition}} check", "‘We’re good to go’ — {{team}} confirms {{driver}} ready after {{condition}} review", "Cleared - {{driver}} set for {{next_race}} — {{condition}} no longer an issue" ] }, { "id": "turning_point_engine_regulation", "new_type": 107, "turning_titles": [ "Late {{type}} engine regulation changes could shake up the grid, insiders say", "Could the FIA's last-minute {{type}} engine rules alter the championship dynamics?", "'It could happen' - whispers of sudden {{type}} engine regulation tweaks", "Speculation grows over potential late {{type}} engine regulation adjustments", "Teams brace for possible last-minute {{type}} engine rule changes", "Rumors of unexpected {{type}} engine regulation shifts circulate in the paddock", "'We're preparing for anything' - teams on possible {{type}} engine rule changes", "Next season engines face uncertainty amid {{type}} regulation change talks", "'It's a possibility' - FIA on potential late {{type}} engine regulation changes", "Insiders hint at sudden {{type}} engine updates for {{change_area}}" ], "positive_titles": [ "FIA implements new {{type}} engine regulations, teams react to the changes", "Confirmed - {{type}} engine regulation changes set to impact next season", "Teams adapt to FIA's new {{type}} engine rules ahead of the upcoming season", "FIA's {{type}} engine regulation overhaul expected to reshape team strategies", "New {{type}} engine regulations announced, teams prepare for the challenges", "FIA's latest {{type}} engine rules spark debate among teams and drivers", "BREAKING - FIA confirms significant {{type}} engine regulation changes", "'We have to adapt quickly' - teams respond to new {{type}} engine regulations", "FIA confirms {{type}} changes focused on {{change_area}}", "Teams prepare for {{type}} {{change_area}} regulation changes" ], "negative_titles": [ "FIA decides against proposed {{type}} engine regulation changes", "No {{type}} changes to engine regulations after FIA review", "Rumors dismissed - {{type}} engine regulations remain unchanged", "FIA confirms no {{type}} alterations to engine rules for the upcoming season", "Relief among teams as FIA maintains current {{type}} engine regulations", "FIA opts not to pursue {{type}} engine regulation changes", "FIA reassures teams with decision to keep {{type}} engine rules intact", "'We found no need for changes' - FIA on maintaining {{type}} engine regulations", "Proposed {{type}} {{change_area}} changes will not go ahead", "FIA shelves {{type}} {{change_area}} regulation changes" ] }, { "id": "young_drivers", "new_type": 108, "turning_titles": [ "The future generation of F1: {{driver1}} and {{driver2}}. Can they make it?", "Rising stars on the radar: {{driver1}} and {{driver2}} turn heads in the junior ranks", "Young guns impress: {{driver1}} and {{driver2}} lead the next wave toward F1", "Paddock buzz grows around {{driver1}} and {{driver2}} as F1's next generation", "Who is next? {{driver1}} and {{driver2}} emerge as standout prospects" ], "positive_titles": [ "F1's future looks bright: {{driver1}} and {{driver2}} earn major praise", "Prospects shine: {{driver1}} and {{driver2}} hailed as the real deal", "Breakout year for {{driver1}} and {{driver2}} fuels F1 hype", "{{driver1}} and {{driver2}} impress teams with standout junior seasons", "Paddock raves about {{driver1}} and {{driver2}} as top prospects" ], "negative_titles": [ "Hype cools on {{driver1}} and {{driver2}} despite junior success", "Not the finished product: doubts linger over {{driver1}} and {{driver2}}", "Paddock splits on {{driver1}} and {{driver2}} as F1 prospects", "{{driver1}} and {{driver2}} face skepticism over F1 readiness", "Promising, but not proven: {{driver1}} and {{driver2}} still have work to do" ] }, { "id": "aduo_engine", "new_type": 109, "turning_titles": [ "Will {{manufacturers}} benefit from the {{number_period}} ADUO window?", "ADUO window opens: {{manufacturers}} eye performance boost", "Finally {{manufacturers}} can make changes during the {{number_period}} ADUO window", "ADUO window arrives: {{manufacturers}} prepare for potential upgrades", "ADUO window opens: {{manufacturers}} ready to optimize performance", "‘We’ve been waiting for this’ – {{manufacturers}} on the {{number_period}} ADUO window", "Sources claim {{manufacturers}} had been preparing for the {{number_period}} ADUO window for months", "{{manufacturers}} ready to make the most of the {{number_period}} ADUO window as it opens", "ADUO window opens: {{manufacturers}} set to unleash performance improvements", "How much will {{manufacturers}} gain from the {{number_period}} ADUO window? Insiders weigh in", "‘It’s a great opportunity’ – {{manufacturers}} on the potential impact of the {{number_period}} ADUO window" ] } ] ================================================ FILE: src/data/nightly_patch_notes.md ================================================ ## 18th january - Added Show/Hide details in standings view to show positions gaines/lodst and gap to leader - Added many team replacement options (Williams -> BMW, Aston -> Racing Poitn, Jaguar, Red Bull -> Ford, Alpine -> Cadilac, Haas -> Toyota) - Added "Season review" in Records tab and made it the defualt screen to see all data from one season - Seasn review items can be clicked and will move the user to that Record/Standings/Comparison view. Team mates head to head can be clicked as well - Added Driver of the day and team records (some) - Added team pictures in records entries ## 21st january - Added "Session Results" in Records dropdown - You can view session results from every session in the current season, only Races/Sprint races from past 5 seasons - Added Edit button in "Session Results" to edit race results. Drag rows to change position, and also ability to change race time ("DNF" will put DNF to that driver) ## 22nd january - Drivers and teams standings are now updated after editing a race result - Records are also updated (not last win/podium) ## 23rd january - Sprint races results are now editable - Label beside¡'s driver names to reflect driver of the day in race results - Sprint qualifying sessions will now show all drivers - Driver of the day now doesn't have % of votes details ## 19th february - Updated calendar UI (drag/delete interactions, hover states, completed icons) - Added driver line ups viewer - Added support for 11 team lineups and team ordering in line ups - Head-to-head headers now use team logos - Added new team themes (Ferrari, Red Bull, Aston Martin, Mercedes, McLaren) plus nightly theme - Team logos resized for improved performance - Stronger contract protection logic (F2/F3 contract bug fixed) ## 28th february - Added 2026 Season mod option in "Mods" tab - Added performance overview in "Performance" tab - Added expertise editing in "Performance" tab - Added engine upgrades over performance chart in "Performance" tab - Added ADUO (Additional Development and Upgrade Opportunities) turning points, toggable from the "Settings" modal and the 2026 season mod tab - Added engine upgrades annotations and annotations toggle in "Performance" tab - Changed F2/F3 logos to improve readability - Minor UI fixes - Fixed bug that would cause F2/F3 teams to have NaN points - Fixed an issue that would prevent the user from toggling "driver line ups" in the 2025 Season DLC if he reloaded the page and uploaded his save with extra drivers again ================================================ FILE: src/data/records.json ================================================ [ { "id": -1, "name": "Michael Schumacher", "firstRace": { "season": 1991, "trackName": "Belgium" }, "firstPodium": { "season": 1992, "trackName": "Mexico" }, "firstWin": { "season": 1992, "trackName": "Belgium" }, "lastWin": { "season": 2006, "trackName": "China" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 7, "totalFastestLaps": 77, "totalPodiums": 155, "totalPointsScored": 1566, "totalPoles": 68, "totalSprintWins": 0, "totalStarts": 306, "totalWins": 91, "value": 0 }, { "id": -1, "name": "Alain Prost", "firstRace": { "season": 1980, "trackName": "Argentina" }, "firstPodium": { "season": 1981, "trackName": "United States" }, "firstWin": { "season": 1981, "trackName": "France" }, "lastWin": { "season": 1993, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 4, "totalFastestLaps": 41, "totalPodiums": 106, "totalPointsScored": 768.5, "totalPoles": 33, "totalSprintWins": 0, "totalStarts": 202, "totalWins": 51, "value": 0 }, { "id": -1, "name": "Ayrton Senna", "firstRace": { "season": 1984, "trackName": "Brazil" }, "firstPodium": { "season": 1984, "trackName": "Monaco" }, "firstWin": { "season": 1985, "trackName": "Portugal" }, "lastWin": { "season": 1993, "trackName": "Australia" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 3, "totalFastestLaps": 19, "totalPodiums": 80, "totalPointsScored": 614, "totalPoles": 65, "totalSprintWins": 0, "totalStarts": 162, "totalWins": 41, "value": 0 }, { "id": -1, "name": "Nigel Mansell", "firstRace": { "season": 1980, "trackName": "Austria" }, "firstPodium": { "season": 1981, "trackName": "Belgium" }, "firstWin": { "season": 1985, "trackName": "Europe" }, "lastWin": { "season": 1994, "trackName": "Australia" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 30, "totalPodiums": 59, "totalPointsScored": 482, "totalPoles": 32, "totalSprintWins": 0, "totalStarts": 187, "totalWins": 31, "value": 0 }, { "id": -1, "name": "Niki Lauda", "firstRace": { "season": 1971, "trackName": "Austria" }, "firstPodium": { "season": 1973, "trackName": "Monaco" }, "firstWin": { "season": 1974, "trackName": "Spain" }, "lastWin": { "season": 1985, "trackName": "Netherlands" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 3, "totalFastestLaps": 24, "totalPodiums": 54, "totalPointsScored": 420.5, "totalPoles": 24, "totalSprintWins": 0, "totalStarts": 171, "totalWins": 25, "value": 0 }, { "id": -1, "name": "Jim Clark", "firstRace": { "season": 1960, "trackName": "Netherlands" }, "firstPodium": { "season": 1962, "trackName": "Netherlands" }, "firstWin": { "season": 1962, "trackName": "Belgium" }, "lastWin": { "season": 1968, "trackName": "South Africa" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 2, "totalFastestLaps": 28, "totalPodiums": 32, "totalPointsScored": 274, "totalPoles": 33, "totalSprintWins": 0, "totalStarts": 72, "totalWins": 25, "value": 0 }, { "id": -1, "name": "Jackie Stewart", "firstRace": { "season": 1965, "trackName": "South Africa" }, "firstPodium": { "season": 1965, "trackName": "South Africa" }, "firstWin": { "season": 1965, "trackName": "Italy" }, "lastWin": { "season": 1973, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 3, "totalFastestLaps": 15, "totalPodiums": 43, "totalPointsScored": 360, "totalPoles": 17, "totalSprintWins": 0, "totalStarts": 99, "totalWins": 27, "value": 0 }, { "id": -1, "name": "Nico Rosberg", "firstRace": { "season": 2006, "trackName": "Bahrain" }, "firstPodium": { "season": 2008, "trackName": "Australia" }, "firstWin": { "season": 2012, "trackName": "China" }, "lastWin": { "season": 2016, "trackName": "Japan" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 20, "totalPodiums": 57, "totalPointsScored": 1594.5, "totalPoles": 30, "totalSprintWins": 0, "totalStarts": 206, "totalWins": 23, "value": 0 }, { "id": -1, "name": "Kimi Räikkönen", "firstRace": { "season": 2001, "trackName": "Australia" }, "firstPodium": { "season": 2002, "trackName": "Australia" }, "firstWin": { "season": 2003, "trackName": "Malaysia" }, "lastWin": { "season": 2018, "trackName": "United States" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 46, "totalPodiums": 103, "totalPointsScored": 1873, "totalPoles": 18, "totalSprintWins": 0, "totalStarts": 349, "totalWins": 21, "value": 0 }, { "id": -1, "name": "Mika Häkkinen", "firstRace": { "season": 1991, "trackName": "United States" }, "firstPodium": { "season": 1993, "trackName": "San Marino" }, "firstWin": { "season": 1997, "trackName": "Europe" }, "lastWin": { "season": 2001, "trackName": "United States" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 2, "totalFastestLaps": 25, "totalPodiums": 51, "totalPointsScored": 420, "totalPoles": 26, "totalSprintWins": 0, "totalStarts": 165, "totalWins": 20, "value": 0 }, { "id": -1, "name": "Juan Manuel Fangio", "firstRace": { "season": 1950, "trackName": "United Kingdom" }, "firstPodium": { "season": 1950, "trackName": "Monaco" }, "firstWin": { "season": 1950, "trackName": "Monaco" }, "lastWin": { "season": 1957, "trackName": "Germany" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 5, "totalFastestLaps": 23, "totalPodiums": 35, "totalPointsScored": 277.64, "totalPoles": 29, "totalSprintWins": 0, "totalStarts": 51, "totalWins": 24, "value": 0 }, { "id": -1, "name": "Nelson Piquet", "firstRace": { "season": 1978, "trackName": "Germany" }, "firstPodium": { "season": 1979, "trackName": "United States" }, "firstWin": { "season": 1980, "trackName": "United States" }, "lastWin": { "season": 1991, "trackName": "Canada" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 3, "totalFastestLaps": 23, "totalPodiums": 60, "totalPointsScored": 485.5, "totalPoles": 24, "totalSprintWins": 0, "totalStarts": 204, "totalWins": 23, "value": 0 }, { "id": -1, "name": "Damon Hill", "firstRace": { "season": 1992, "trackName": "United Kingdom" }, "firstPodium": { "season": 1993, "trackName": "Spain" }, "firstWin": { "season": 1993, "trackName": "Hungary" }, "lastWin": { "season": 1998, "trackName": "Belgium" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 19, "totalPodiums": 42, "totalPointsScored": 360, "totalPoles": 20, "totalSprintWins": 0, "totalStarts": 115, "totalWins": 22, "value": 0 }, { "id": -1, "name": "Stirling Moss", "firstRace": { "season": 1951, "trackName": "Switzerland" }, "firstPodium": { "season": 1954, "trackName": "Belgium" }, "firstWin": { "season": 1955, "trackName": "United Kingdom" }, "lastWin": { "season": 1961, "trackName": "Germany" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 19, "totalPodiums": 24, "totalPointsScored": 186, "totalPoles": 16, "totalSprintWins": 0, "totalStarts": 67, "totalWins": 16, "value": 0 }, { "id": -1, "name": "Graham Hill", "firstRace": { "season": 1958, "trackName": "Monaco" }, "firstPodium": { "season": 1960, "trackName": "Netherlands" }, "firstWin": { "season": 1962, "trackName": "Netherlands" }, "lastWin": { "season": 1969, "trackName": "Monaco" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 2, "totalFastestLaps": 10, "totalPodiums": 36, "totalPointsScored": 289, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 176, "totalWins": 14, "value": 0 }, { "id": -1, "name": "Emerson Fittipaldi", "firstRace": { "season": 1970, "trackName": "United Kingdom" }, "firstPodium": { "season": 1970, "trackName": "United States" }, "firstWin": { "season": 1970, "trackName": "United States" }, "lastWin": { "season": 1975, "trackName": "Great Britain" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 2, "totalFastestLaps": 6, "totalPodiums": 35, "totalPointsScored": 281, "totalPoles": 6, "totalSprintWins": 0, "totalStarts": 144, "totalWins": 14, "value": 0 }, { "id": -1, "name": "Jack Brabham", "firstRace": { "season": 1955, "trackName": "United Kingdom" }, "firstPodium": { "season": 1958, "trackName": "Monaco" }, "firstWin": { "season": 1959, "trackName": "Monaco" }, "lastWin": { "season": 1970, "trackName": "South Africa" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 3, "totalFastestLaps": 12, "totalPodiums": 31, "totalPointsScored": 261, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 128, "totalWins": 14, "value": 0 }, { "id": -1, "name": "Alberto Ascari", "firstRace": { "season": 1950, "trackName": "Monaco" }, "firstPodium": { "season": 1950, "trackName": "Italy" }, "firstWin": { "season": 1951, "trackName": "Germany" }, "lastWin": { "season": 1953, "trackName": "Switzerland" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 2, "totalFastestLaps": 12, "totalPodiums": 17, "totalPointsScored": 140, "totalPoles": 14, "totalSprintWins": 0, "totalStarts": 32, "totalWins": 13, "value": 0 }, { "id": -1, "name": "David Coulthard", "firstRace": { "season": 1994, "trackName": "Spain" }, "firstPodium": { "season": 1994, "trackName": "Italy" }, "firstWin": { "season": 1995, "trackName": "Portugal" }, "lastWin": { "season": 2003, "trackName": "Australia" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 18, "totalPodiums": 62, "totalPointsScored": 535, "totalPoles": 12, "totalSprintWins": 0, "totalStarts": 247, "totalWins": 13, "value": 0 }, { "id": -1, "name": "Carlos Reutemann", "firstRace": { "season": 1972, "trackName": "Argentina" }, "firstPodium": { "season": 1972, "trackName": "South Africa" }, "firstWin": { "season": 1974, "trackName": "South Africa" }, "lastWin": { "season": 1981, "trackName": "Belgium" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 6, "totalPodiums": 45, "totalPointsScored": 309, "totalPoles": 24, "totalSprintWins": 0, "totalStarts": 146, "totalWins": 12, "value": 0 }, { "id": -1, "name": "Mario Andretti", "firstRace": { "season": 1968, "trackName": "United States" }, "firstPodium": { "season": 1970, "trackName": "Spain" }, "firstWin": { "season": 1971, "trackName": "South Africa" }, "lastWin": { "season": 1978, "trackName": "Netherlands" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 10, "totalPodiums": 19, "totalPointsScored": 180, "totalPoles": 18, "totalSprintWins": 0, "totalStarts": 128, "totalWins": 12, "value": 0 }, { "id": -1, "name": "Rubens Barrichello", "firstRace": { "season": 1993, "trackName": "South Africa" }, "firstPodium": { "season": 1994, "trackName": "Pacific" }, "firstWin": { "season": 2000, "trackName": "Germany" }, "lastWin": { "season": 2009, "trackName": "Italy" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 17, "totalPodiums": 68, "totalPointsScored": 658, "totalPoles": 14, "totalSprintWins": 0, "totalStarts": 322, "totalWins": 11, "value": 0 }, { "id": -1, "name": "Jacques Villeneuve", "firstRace": { "season": 1996, "trackName": "Australia" }, "firstPodium": { "season": 1996, "trackName": "Australia" }, "firstWin": { "season": 1996, "trackName": "Europe" }, "lastWin": { "season": 1997, "trackName": "Luxembourg" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 9, "totalPodiums": 23, "totalPointsScored": 235, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 165, "totalWins": 11, "value": 0 }, { "id": -1, "name": "Gerhard Berger", "firstRace": { "season": 1984, "trackName": "Austria" }, "firstPodium": { "season": 1986, "trackName": "Italy" }, "firstWin": { "season": 1986, "trackName": "Mexico" }, "lastWin": { "season": 1997, "trackName": "Germany" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 19, "totalPodiums": 48, "totalPointsScored": 385, "totalPoles": 12, "totalSprintWins": 0, "totalStarts": 210, "totalWins": 10, "value": 0 }, { "id": -1, "name": "Ronnie Peterson", "firstRace": { "season": 1970, "trackName": "Monaco" }, "firstPodium": { "season": 1971, "trackName": "Italy" }, "firstWin": { "season": 1973, "trackName": "France" }, "lastWin": { "season": 1978, "trackName": "Austria" }, "record": "wins", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 9, "totalPodiums": 26, "totalPointsScored": 206, "totalPoles": 14, "totalSprintWins": 0, "totalStarts": 123, "totalWins": 10, "value": 0 }, { "id": -1, "name": "Jenson Button", "firstRace": { "season": 2000, "trackName": "Australia" }, "firstPodium": { "season": 2004, "trackName": "Malaysia" }, "firstWin": { "season": 2006, "trackName": "Hungary" }, "lastWin": { "season": 2012, "trackName": "Brazil" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 8, "totalPodiums": 50, "totalPointsScored": 1235, "totalPoles": 8, "totalSprintWins": 0, "totalStarts": 306, "totalWins": 15, "value": 0 }, { "id": -1, "name": "Alan Jones", "firstRace": { "season": 1975, "trackName": "Spain" }, "firstPodium": { "season": 1977, "trackName": "France" }, "firstWin": { "season": 1977, "trackName": "Austria" }, "lastWin": { "season": 1981, "trackName": "United States" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 13, "totalPodiums": 24, "totalPointsScored": 206, "totalPoles": 6, "totalSprintWins": 0, "totalStarts": 116, "totalWins": 12, "value": 0 }, { "id": -1, "name": "Mark Webber", "firstRace": { "season": 2002, "trackName": "Australia" }, "firstPodium": { "season": 2005, "trackName": "Monaco" }, "firstWin": { "season": 2009, "trackName": "Germany" }, "lastWin": { "season": 2012, "trackName": "United Kingdom" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 19, "totalPodiums": 42, "totalPointsScored": 1047.5, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 215, "totalWins": 9, "value": 0 }, { "id": -1, "name": "Denny Hulme", "firstRace": { "season": 1965, "trackName": "Monaco" }, "firstPodium": { "season": 1965, "trackName": "France" }, "firstWin": { "season": 1967, "trackName": "Monaco" }, "lastWin": { "season": 1974, "trackName": "Argentina" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 9, "totalPodiums": 33, "totalPointsScored": 248, "totalPoles": 1, "totalSprintWins": 0, "totalStarts": 112, "totalWins": 8, "value": 0 }, { "id": -1, "name": "Jacky Ickx", "firstRace": { "season": 1966, "trackName": "Germany" }, "firstPodium": { "season": 1968, "trackName": "France" }, "firstWin": { "season": 1968, "trackName": "France" }, "lastWin": { "season": 1972, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 14, "totalPodiums": 25, "totalPointsScored": 181, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 116, "totalWins": 8, "value": 0 }, { "id": -1, "name": "René Arnoux", "firstRace": { "season": 1978, "trackName": "South Africa" }, "firstPodium": { "season": 1979, "trackName": "United Kingdom" }, "firstWin": { "season": 1980, "trackName": "Brazil" }, "lastWin": { "season": 1983, "trackName": "Netherlands" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 12, "totalPodiums": 22, "totalPointsScored": 181, "totalPoles": 18, "totalSprintWins": 0, "totalStarts": 149, "totalWins": 7, "value": 0 }, { "id": -1, "name": "Juan Pablo Montoya", "firstRace": { "season": 2001, "trackName": "Australia" }, "firstPodium": { "season": 2001, "trackName": "Spain" }, "firstWin": { "season": 2001, "trackName": "Italy" }, "lastWin": { "season": 2005, "trackName": "Brazil" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 12, "totalPodiums": 30, "totalPointsScored": 307, "totalPoles": 13, "totalSprintWins": 0, "totalStarts": 94, "totalWins": 7, "value": 0 }, { "id": -1, "name": "Gilles Villeneuve", "firstRace": { "season": 1977, "trackName": "United Kingdom" }, "firstPodium": { "season": 1978, "trackName": "United States" }, "firstWin": { "season": 1978, "trackName": "Canada" }, "lastWin": { "season": 1981, "trackName": "Spain" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 8, "totalPodiums": 13, "totalPointsScored": 107, "totalPoles": 2, "totalSprintWins": 0, "totalStarts": 67, "totalWins": 6, "value": 0 }, { "id": -1, "name": "Jochen Rindt", "firstRace": { "season": 1964, "trackName": "Austria" }, "firstPodium": { "season": 1968, "trackName": "Germany" }, "firstWin": { "season": 1969, "trackName": "United States" }, "lastWin": { "season": 1970, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 3, "totalPodiums": 13, "totalPointsScored": 109, "totalPoles": 10, "totalSprintWins": 0, "totalStarts": 60, "totalWins": 6, "value": 0 }, { "id": -1, "name": "Ralf Schumacher", "firstRace": { "season": 1997, "trackName": "Australia" }, "firstPodium": { "season": 1997, "trackName": "Argentina" }, "firstWin": { "season": 2001, "trackName": "San Marino" }, "lastWin": { "season": 2003, "trackName": "France" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 8, "totalPodiums": 27, "totalPointsScored": 329, "totalPoles": 6, "totalSprintWins": 0, "totalStarts": 180, "totalWins": 6, "value": 0 }, { "id": -1, "name": "James Hunt", "firstRace": { "season": 1973, "trackName": "Monaco" }, "firstPodium": { "season": 1973, "trackName": "Netherlands" }, "firstWin": { "season": 1975, "trackName": "Netherlands" }, "lastWin": { "season": 1977, "trackName": "Japan" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 8, "totalPodiums": 23, "totalPointsScored": 179, "totalPoles": 14, "totalSprintWins": 0, "totalStarts": 92, "totalWins": 10, "value": 0 }, { "id": -1, "name": "Jody Scheckter", "firstRace": { "season": 1972, "trackName": "United States" }, "firstPodium": { "season": 1973, "trackName": "France" }, "firstWin": { "season": 1974, "trackName": "Sweden" }, "lastWin": { "season": 1979, "trackName": "Italy" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 5, "totalPodiums": 33, "totalPointsScored": 255, "totalPoles": 3, "totalSprintWins": 0, "totalStarts": 112, "totalWins": 10, "value": 0 }, { "id": -1, "name": "Felipe Massa", "firstRace": { "season": 2002, "trackName": "Australia" }, "firstPodium": { "season": 2006, "trackName": "Europe" }, "firstWin": { "season": 2006, "trackName": "Turkey" }, "lastWin": { "season": 2008, "trackName": "Brazil" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 15, "totalPodiums": 41, "totalPointsScored": 1167, "totalPoles": 16, "totalSprintWins": 0, "totalStarts": 269, "totalWins": 11, "value": 0 }, { "id": -1, "name": "John Surtees", "firstRace": { "season": 1960, "trackName": "Monaco" }, "firstPodium": { "season": 1960, "trackName": "United Kingdom" }, "firstWin": { "season": 1963, "trackName": "Germany" }, "lastWin": { "season": 1967, "trackName": "Italy" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 10, "totalPodiums": 24, "totalPointsScored": 180, "totalPoles": 8, "totalSprintWins": 0, "totalStarts": 111, "totalWins": 6, "value": 0 }, { "id": -1, "name": "Tony Brooks", "firstRace": { "season": 1956, "trackName": "Monaco" }, "firstPodium": { "season": 1957, "trackName": "Monaco" }, "firstWin": { "season": 1957, "trackName": "United Kingdom" }, "lastWin": { "season": 1959, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 3, "totalPodiums": 10, "totalPointsScored": 75, "totalPoles": 3, "totalSprintWins": 0, "totalStarts": 38, "totalWins": 6, "value": 0 }, { "id": -1, "name": "Jacques Laffite", "firstRace": { "season": 1974, "trackName": "Germany" }, "firstPodium": { "season": 1975, "trackName": "Germany" }, "firstWin": { "season": 1977, "trackName": "Sweden" }, "lastWin": { "season": 1981, "trackName": "Canada" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 7, "totalPodiums": 32, "totalPointsScored": 228, "totalPoles": 7, "totalSprintWins": 0, "totalStarts": 176, "totalWins": 6, "value": 0 }, { "id": -1, "name": "Keke Rosberg", "firstRace": { "season": 1978, "trackName": "South Africa" }, "firstPodium": { "season": 1980, "trackName": "Argentina" }, "firstWin": { "season": 1982, "trackName": "Switzerland" }, "lastWin": { "season": 1985, "trackName": "Australia" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 3, "totalPodiums": 17, "totalPointsScored": 159.5, "totalPoles": 5, "totalSprintWins": 0, "totalStarts": 114, "totalWins": 5, "value": 0 }, { "id": -1, "name": "Michele Alboreto", "firstRace": { "season": 1981, "trackName": "Italy" }, "firstPodium": { "season": 1982, "trackName": "Italy" }, "firstWin": { "season": 1982, "trackName": "United States" }, "lastWin": { "season": 1985, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 5, "totalPodiums": 23, "totalPointsScored": 186.5, "totalPoles": 2, "totalSprintWins": 0, "totalStarts": 194, "totalWins": 5, "value": 0 }, { "id": -1, "name": "Clay Regazzoni", "firstRace": { "season": 1970, "trackName": "Netherlands" }, "firstPodium": { "season": 1970, "trackName": "Netherlands" }, "firstWin": { "season": 1970, "trackName": "Italy" }, "lastWin": { "season": 1979, "trackName": "United Kingdom" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 15, "totalPodiums": 28, "totalPointsScored": 209, "totalPoles": 5, "totalSprintWins": 0, "totalStarts": 132, "totalWins": 5, "value": 0 }, { "id": -1, "name": "John Watson", "firstRace": { "season": 1973, "trackName": "United Kingdom" }, "firstPodium": { "season": 1976, "trackName": "France" }, "firstWin": { "season": 1976, "trackName": "Austria" }, "lastWin": { "season": 1983, "trackName": "United States" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 5, "totalPodiums": 20, "totalPointsScored": 169, "totalPoles": 2, "totalSprintWins": 0, "totalStarts": 152, "totalWins": 5, "value": 0 }, { "id": -1, "name": "Giuseppe Farina", "firstRace": { "season": 1950, "trackName": "United Kingdom" }, "firstPodium": { "season": 1950, "trackName": "United Kingdom" }, "firstWin": { "season": 1950, "trackName": "United Kingdom" }, "lastWin": { "season": 1953, "trackName": "Germany" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 1, "totalFastestLaps": 5, "totalPodiums": 20, "totalPointsScored": 115, "totalPoles": 5, "totalSprintWins": 0, "totalStarts": 33, "totalWins": 5, "value": 0 }, { "id": -1, "name": "Thierry Boutsen", "firstRace": { "season": 1983, "trackName": "Belgium" }, "firstPodium": { "season": 1988, "trackName": "Canada" }, "firstWin": { "season": 1989, "trackName": "Canada" }, "lastWin": { "season": 1990, "trackName": "Hungary" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 1, "totalPodiums": 15, "totalPointsScored": 132, "totalPoles": 1, "totalSprintWins": 0, "totalStarts": 163, "totalWins": 3, "value": 0 }, { "id": -1, "name": "Patrick Tambay", "firstRace": { "season": 1977, "trackName": "France" }, "firstPodium": { "season": 1982, "trackName": "United Kingdom" }, "firstWin": { "season": 1982, "trackName": "Germany" }, "lastWin": { "season": 1983, "trackName": "Italy" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 2, "totalPodiums": 11, "totalPointsScored": 103, "totalPoles": 5, "totalSprintWins": 0, "totalStarts": 114, "totalWins": 2, "value": 0 }, { "id": -1, "name": "Peter Revson", "firstRace": { "season": 1964, "trackName": "Monaco" }, "firstPodium": { "season": 1972, "trackName": "Canada" }, "firstWin": { "season": 1973, "trackName": "United Kingdom" }, "lastWin": { "season": 1973, "trackName": "Canada" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 0, "totalPodiums": 8, "totalPointsScored": 61, "totalPoles": 1, "totalSprintWins": 0, "totalStarts": 30, "totalWins": 2, "value": 0 }, { "id": -1, "name": "Jean-Pierre Beltoise", "firstRace": { "season": 1966, "trackName": "Germany" }, "firstPodium": { "season": 1968, "trackName": "Netherlands" }, "firstWin": { "season": 1972, "trackName": "Monaco" }, "lastWin": { "season": 1972, "trackName": "Monaco" }, "record": "", "retired": 1, "teamId": -1, "totalChampionshipWins": 0, "totalFastestLaps": 4, "totalPodiums": 8, "totalPointsScored": 77, "totalPoles": 0, "totalSprintWins": 0, "totalStarts": 86, "totalWins": 1, "value": 0 } ] ================================================ FILE: src/data/tables_2026.json ================================================ { "Staff_BasicData": [ { "AgeType": null, "CountryID": 186, "DOB": 31054, "DOB_ISO": "1985-01-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Lewis]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hamilton]", "PhotoDay": 45340, "StaffID": 1 }, { "AgeType": null, "CountryID": 111, "DOB": 35719, "DOB_ISO": "1997-10-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Charles]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Leclerc]", "PhotoDay": 45340, "StaffID": 2 }, { "AgeType": null, "CountryID": 171, "DOB": 35147, "DOB_ISO": "1996-03-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alexander]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Albon]", "PhotoDay": 45340, "StaffID": 3 }, { "AgeType": null, "CountryID": 79, "DOB": 27489, "DOB_ISO": "1975-04-05", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Enrico]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cardile]", "PhotoDay": 45340, "StaffID": 4 }, { "AgeType": null, "CountryID": 57, "DOB": 23012, "DOB_ISO": "1963-01-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Bruno]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Famin]", "PhotoDay": 45340, "StaffID": 5 }, { "AgeType": null, "CountryID": 160, "DOB": 29061, "DOB_ISO": "1979-07-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Xavier]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_MarcosPadros]", "PhotoDay": 45340, "StaffID": 6 }, { "AgeType": null, "CountryID": 79, "DOB": 26995, "DOB_ISO": "1973-11-27", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Riccardo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Adami]", "PhotoDay": 45340, "StaffID": 7 }, { "AgeType": null, "CountryID": 56, "DOB": 32748, "DOB_ISO": "1989-08-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Valtteri]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bottas]", "PhotoDay": 45340, "StaffID": 8 }, { "AgeType": null, "CountryID": 60, "DOB": 31961, "DOB_ISO": "1987-07-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sebastian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vettel]", "PhotoDay": 45340, "StaffID": 9 }, { "AgeType": null, "CountryID": 120, "DOB": 35703, "DOB_ISO": "1997-09-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Max]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Verstappen]", "PhotoDay": 45340, "StaffID": 10 }, { "AgeType": null, "CountryID": 160, "DOB": 34578, "DOB_ISO": "1994-09-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Carlos]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Sainz]", "PhotoDay": 45340, "StaffID": 11 }, { "AgeType": null, "CountryID": 186, "DOB": 36477, "DOB_ISO": "1999-11-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Lando]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Norris]", "PhotoDay": 45340, "StaffID": 12 }, { "AgeType": null, "CountryID": 9, "DOB": 32690, "DOB_ISO": "1989-07-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Daniel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ricciardo]", "PhotoDay": 45340, "StaffID": 13 }, { "AgeType": null, "CountryID": 57, "DOB": 35325, "DOB_ISO": "1996-09-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Esteban]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ocon]", "PhotoDay": 45340, "StaffID": 14 }, { "AgeType": null, "CountryID": 57, "DOB": 35102, "DOB_ISO": "1996-02-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pierre]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gasly]", "PhotoDay": 45340, "StaffID": 15 }, { "AgeType": null, "CountryID": 108, "DOB": 32899, "DOB_ISO": "1990-01-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sergio]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Perez1]", "PhotoDay": 45340, "StaffID": 17 }, { "AgeType": null, "CountryID": 30, "DOB": 36097, "DOB_ISO": "1998-10-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Lance]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stroll]", "PhotoDay": 45340, "StaffID": 18 }, { "AgeType": null, "CountryID": 79, "DOB": 34317, "DOB_ISO": "1993-12-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Antonio]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Giovinazzi]", "PhotoDay": 45340, "StaffID": 20 }, { "AgeType": null, "CountryID": 16, "DOB": 33689, "DOB_ISO": "1992-03-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Stoffel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vandoorne]", "PhotoDay": 45340, "StaffID": 22 }, { "AgeType": null, "CountryID": 186, "DOB": 35841, "DOB_ISO": "1998-02-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_George]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Russell]", "PhotoDay": 45340, "StaffID": 23 }, { "AgeType": null, "CountryID": 79, "DOB": 30003, "DOB_ISO": "1982-02-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Diego]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Tondi]", "PhotoDay": 45340, "StaffID": 26 }, { "AgeType": null, "CountryID": 57, "DOB": 26939, "DOB_ISO": "1973-10-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pierre]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wache1]", "PhotoDay": 45340, "StaffID": 28 }, { "AgeType": null, "CountryID": 186, "DOB": 28328, "DOB_ISO": "1977-07-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matt]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Harman]", "PhotoDay": 45340, "StaffID": 30 }, { "AgeType": null, "CountryID": 186, "DOB": 27057, "DOB_ISO": "1974-01-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jody]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Egginton]", "PhotoDay": 45340, "StaffID": 33 }, { "AgeType": null, "CountryID": 79, "DOB": 28879, "DOB_ISO": "1979-01-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Enrico]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Balbo]", "PhotoDay": 45340, "StaffID": 37 }, { "AgeType": null, "CountryID": 186, "DOB": 27184, "DOB_ISO": "1974-06-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jarrod]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Murphy]", "PhotoDay": 45340, "StaffID": 38 }, { "AgeType": null, "CountryID": 157, "DOB": 23140, "DOB_ISO": "1963-05-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Dirk]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_deBeer]", "PhotoDay": 45340, "StaffID": 39 }, { "AgeType": null, "CountryID": 186, "DOB": 28214, "DOB_ISO": "1977-03-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_David]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wheater]", "PhotoDay": 45340, "StaffID": 40 }, { "AgeType": null, "CountryID": 79, "DOB": 27513, "DOB_ISO": "1975-04-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alessandro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cinelli]", "PhotoDay": 45340, "StaffID": 43 }, { "AgeType": null, "CountryID": 186, "DOB": 28592, "DOB_ISO": "1978-04-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Greig]", "PhotoDay": 45340, "StaffID": 44 }, { "AgeType": null, "CountryID": 186, "DOB": 28571, "DOB_ISO": "1978-03-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Tom]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stallard]", "PhotoDay": 45340, "StaffID": 47 }, { "AgeType": null, "CountryID": 186, "DOB": 30484, "DOB_ISO": "1983-06-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_William]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Joseph]", "PhotoDay": 45340, "StaffID": 48 }, { "AgeType": null, "CountryID": 186, "DOB": 32458, "DOB_ISO": "1988-11-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Hugh]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bird]", "PhotoDay": 45340, "StaffID": 49 }, { "AgeType": null, "CountryID": 79, "DOB": 29132, "DOB_ISO": "1979-10-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gianpiero]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Lambiase]", "PhotoDay": 45340, "StaffID": 50 }, { "AgeType": null, "CountryID": 186, "DOB": 27630, "DOB_ISO": "1975-08-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Peter]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bonnington]", "PhotoDay": 45340, "StaffID": 51 }, { "AgeType": null, "CountryID": 186, "DOB": 32724, "DOB_ISO": "1989-08-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Josh]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Peckett]", "PhotoDay": 45340, "StaffID": 54 }, { "AgeType": null, "CountryID": 186, "DOB": 29261, "DOB_ISO": "1980-02-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_James]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Urwin]", "PhotoDay": 45340, "StaffID": 55 }, { "AgeType": null, "CountryID": 57, "DOB": 29911, "DOB_ISO": "1981-11-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gaetan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Jego]", "PhotoDay": 45340, "StaffID": 56 }, { "AgeType": null, "CountryID": 186, "DOB": 28093, "DOB_ISO": "1976-11-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gary]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gannon]", "PhotoDay": 45340, "StaffID": 58 }, { "AgeType": null, "CountryID": 57, "DOB": 31313, "DOB_ISO": "1985-09-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pierre]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hamelin]", "PhotoDay": 45340, "StaffID": 59 }, { "AgeType": null, "CountryID": 79, "DOB": 31978, "DOB_ISO": "1987-07-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mattia]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Spini]", "PhotoDay": 45340, "StaffID": 60 }, { "AgeType": null, "CountryID": 186, "DOB": 33005, "DOB_ISO": "1990-05-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alex]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Chan]", "PhotoDay": 45340, "StaffID": 61 }, { "AgeType": null, "CountryID": 186, "DOB": 30588, "DOB_ISO": "1983-09-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Chris]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cronin]", "PhotoDay": 45340, "StaffID": 63 }, { "AgeType": null, "CountryID": 186, "DOB": 26371, "DOB_ISO": "1972-03-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ben]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Michell]", "PhotoDay": 45340, "StaffID": 64 }, { "AgeType": null, "CountryID": 60, "DOB": 36241, "DOB_ISO": "1999-03-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mick]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Schumacher]", "PhotoDay": 45340, "StaffID": 74 }, { "AgeType": null, "CountryID": 120, "DOB": 34736, "DOB_ISO": "1995-02-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nyck]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_deVries]", "PhotoDay": 45340, "StaffID": 76 }, { "AgeType": null, "CountryID": 160, "DOB": 29796, "DOB_ISO": "1981-07-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Fernando]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Alonso]", "PhotoDay": 45340, "StaffID": 77 }, { "AgeType": null, "CountryID": 78, "DOB": 34668, "DOB_ISO": "1994-11-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Roy]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Nissany]", "PhotoDay": 45340, "StaffID": 78 }, { "AgeType": null, "CountryID": 23, "DOB": 35241, "DOB_ISO": "1996-06-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pietro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fittipaldi]", "PhotoDay": 45340, "StaffID": 80 }, { "AgeType": null, "CountryID": 82, "DOB": 36657, "DOB_ISO": "2000-05-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Yuki]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Tsunoda]", "PhotoDay": 45340, "StaffID": 81 }, { "AgeType": null, "CountryID": 135, "DOB": 31023, "DOB_ISO": "1984-12-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Robert]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Kubica]", "PhotoDay": 45340, "StaffID": 82 }, { "AgeType": null, "CountryID": 60, "DOB": 32008, "DOB_ISO": "1987-08-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nico]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hulkenberg1]", "PhotoDay": 45340, "StaffID": 83 }, { "AgeType": null, "CountryID": 57, "DOB": 37853, "DOB_ISO": "2003-08-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Theo1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Pourchaire]", "PhotoDay": 45340, "StaffID": 87 }, { "AgeType": null, "CountryID": 167, "DOB": 35696, "DOB_ISO": "1997-09-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ralph]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Boschung]", "PhotoDay": 45340, "StaffID": 88 }, { "AgeType": null, "CountryID": 73, "DOB": 36069, "DOB_ISO": "1998-10-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jehan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Daruvala]", "PhotoDay": 45340, "StaffID": 91 }, { "AgeType": null, "CountryID": 121, "DOB": 37298, "DOB_ISO": "2002-02-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Liam]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Lawson]", "PhotoDay": 45340, "StaffID": 95 }, { "AgeType": null, "CountryID": 120, "DOB": 36876, "DOB_ISO": "2000-12-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Richard]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Verschoor]", "PhotoDay": 45340, "StaffID": 99 }, { "AgeType": null, "CountryID": 9, "DOB": 36987, "DOB_ISO": "2001-04-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Oscar]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Piastri]", "PhotoDay": 45340, "StaffID": 102 }, { "AgeType": null, "CountryID": 35, "DOB": 36341, "DOB_ISO": "1999-06-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Guanyu]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Zhou]", "PhotoDay": 45340, "StaffID": 105 }, { "AgeType": null, "CountryID": 23, "DOB": 36669, "DOB_ISO": "2000-05-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Felipe]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Drugovich]", "PhotoDay": 45340, "StaffID": 106 }, { "AgeType": null, "CountryID": 43, "DOB": 37269, "DOB_ISO": "2002-01-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Frederik]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vesti]", "PhotoDay": 45340, "StaffID": 107 }, { "AgeType": null, "CountryID": 187, "DOB": 36381, "DOB_ISO": "1999-08-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_JuanManuel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Correa]", "PhotoDay": 45340, "StaffID": 109 }, { "AgeType": null, "CountryID": 16, "DOB": 37446, "DOB_ISO": "2002-07-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Amaury]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cordeel]", "PhotoDay": 45340, "StaffID": 110 }, { "AgeType": null, "CountryID": 78, "DOB": 37119, "DOB_ISO": "2001-08-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ido]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cohen]", "PhotoDay": 45340, "StaffID": 114 }, { "AgeType": null, "CountryID": 187, "DOB": 37411, "DOB_ISO": "2002-06-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kaylen]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Frederick]", "PhotoDay": 45340, "StaffID": 115 }, { "AgeType": null, "CountryID": 187, "DOB": 36891, "DOB_ISO": "2000-12-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Logan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Sargeant]", "PhotoDay": 45340, "StaffID": 116 }, { "AgeType": null, "CountryID": 23, "DOB": 37090, "DOB_ISO": "2001-07-18", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Enzo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fittipaldi]", "PhotoDay": 45340, "StaffID": 117 }, { "AgeType": null, "CountryID": 42, "DOB": 38042, "DOB_ISO": "2004-02-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Roman]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stanek1]", "PhotoDay": 45340, "StaffID": 119 }, { "AgeType": null, "CountryID": 82, "DOB": 37156, "DOB_ISO": "2001-09-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ayumu]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Iwasa]", "PhotoDay": 45340, "StaffID": 120 }, { "AgeType": null, "CountryID": 187, "DOB": 38474, "DOB_ISO": "2005-05-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jak]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Crawford]", "PhotoDay": 45340, "StaffID": 121 }, { "AgeType": null, "CountryID": 108, "DOB": 37205, "DOB_ISO": "2001-11-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Rafael]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Villagomez1]", "PhotoDay": 45340, "StaffID": 123 }, { "AgeType": null, "CountryID": 57, "DOB": 37058, "DOB_ISO": "2001-06-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Victor]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Martins]", "PhotoDay": 45340, "StaffID": 127 }, { "AgeType": null, "CountryID": 23, "DOB": 37349, "DOB_ISO": "2002-04-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Caio]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Collet]", "PhotoDay": 45340, "StaffID": 128 }, { "AgeType": null, "CountryID": 126, "DOB": 37697, "DOB_ISO": "2003-03-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Dennis]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hauger]", "PhotoDay": 45340, "StaffID": 130 }, { "AgeType": null, "CountryID": 111, "DOB": 36813, "DOB_ISO": "2000-10-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Arthur]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Leclerc]", "PhotoDay": 45340, "StaffID": 132 }, { "AgeType": null, "CountryID": 57, "DOB": 36883, "DOB_ISO": "2000-12-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Clement1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Novalak]", "PhotoDay": 45340, "StaffID": 133 }, { "AgeType": null, "CountryID": 9, "DOB": 37641, "DOB_ISO": "2003-01-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jack]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Doohan]", "PhotoDay": 45340, "StaffID": 135 }, { "AgeType": null, "CountryID": 186, "DOB": 34484, "DOB_ISO": "1994-05-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jake]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hughes]", "PhotoDay": 45340, "StaffID": 140 }, { "AgeType": null, "CountryID": 186, "DOB": 38480, "DOB_ISO": "2005-05-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Oliver]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bearman]", "PhotoDay": 45340, "StaffID": 142 }, { "AgeType": null, "CountryID": 167, "DOB": 36520, "DOB_ISO": "1999-12-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gregoire1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Saucy]", "PhotoDay": 45340, "StaffID": 143 }, { "AgeType": null, "CountryID": 57, "DOB": 38258, "DOB_ISO": "2004-09-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Isack]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hadjar]", "PhotoDay": 45340, "StaffID": 144 }, { "AgeType": null, "CountryID": 57, "DOB": 33370, "DOB_ISO": "1991-05-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Clement1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Sudre]", "PhotoDay": 45340, "StaffID": 145 }, { "AgeType": null, "CountryID": 57, "DOB": 33389, "DOB_ISO": "1991-05-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Amaury]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Lardon]", "PhotoDay": 45340, "StaffID": 146 }, { "AgeType": null, "CountryID": 186, "DOB": 28171, "DOB_ISO": "1977-02-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Stuart]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_King]", "PhotoDay": 45340, "StaffID": 148 }, { "AgeType": null, "CountryID": 186, "DOB": 29964, "DOB_ISO": "1982-01-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mathew]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ogle]", "PhotoDay": 45340, "StaffID": 149 }, { "AgeType": null, "CountryID": 57, "DOB": 32856, "DOB_ISO": "1989-12-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Romain]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Goasguen]", "PhotoDay": 45340, "StaffID": 151 }, { "AgeType": null, "CountryID": 79, "DOB": 26163, "DOB_ISO": "1971-08-18", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Paolo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Angilella]", "PhotoDay": 45340, "StaffID": 152 }, { "AgeType": null, "CountryID": 79, "DOB": 29416, "DOB_ISO": "1980-07-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mattia]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Oselladore]", "PhotoDay": 45340, "StaffID": 153 }, { "AgeType": null, "CountryID": 136, "DOB": 33804, "DOB_ISO": "1992-07-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pedro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Matos]", "PhotoDay": 45340, "StaffID": 155 }, { "AgeType": null, "CountryID": 79, "DOB": 30646, "DOB_ISO": "1983-11-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mario]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Garigulo]", "PhotoDay": 45340, "StaffID": 156 }, { "AgeType": null, "CountryID": 186, "DOB": 28872, "DOB_ISO": "1979-01-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Geoff]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Spear]", "PhotoDay": 45340, "StaffID": 158 }, { "AgeType": null, "CountryID": 160, "DOB": 31149, "DOB_ISO": "1985-04-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Pau]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Rivera]", "PhotoDay": 45340, "StaffID": 159 }, { "AgeType": null, "CountryID": 57, "DOB": 31326, "DOB_ISO": "1985-10-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Queille]", "PhotoDay": 45340, "StaffID": 160 }, { "AgeType": null, "CountryID": 60, "DOB": 32118, "DOB_ISO": "1987-12-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Sumann]", "PhotoDay": 45340, "StaffID": 161 }, { "AgeType": null, "CountryID": 160, "DOB": 31369, "DOB_ISO": "1985-11-18", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pol]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Andreu]", "PhotoDay": 45340, "StaffID": 162 }, { "AgeType": null, "CountryID": 160, "DOB": 33088, "DOB_ISO": "1990-08-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Adrian1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Rodriguez1]", "PhotoDay": 45340, "StaffID": 163 }, { "AgeType": null, "CountryID": 57, "DOB": 34688, "DOB_ISO": "1994-12-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Thomas]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Leroy]", "PhotoDay": 45340, "StaffID": 165 }, { "AgeType": null, "CountryID": 57, "DOB": 33984, "DOB_ISO": "1993-01-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Thomas]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Tessore]", "PhotoDay": 45340, "StaffID": 166 }, { "AgeType": null, "CountryID": 120, "DOB": 30371, "DOB_ISO": "1983-02-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Peter]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_vanLeeuwen]", "PhotoDay": 45340, "StaffID": 168 }, { "AgeType": null, "CountryID": 79, "DOB": 32367, "DOB_ISO": "1988-08-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Carlo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cristofori]", "PhotoDay": 45340, "StaffID": 169 }, { "AgeType": null, "CountryID": 79, "DOB": 23736, "DOB_ISO": "1964-12-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andrea]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_TesiMancini]", "PhotoDay": 45340, "StaffID": 172 }, { "AgeType": null, "CountryID": 79, "DOB": 23398, "DOB_ISO": "1964-01-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Umberto]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Visintini]", "PhotoDay": 45340, "StaffID": 173 }, { "AgeType": null, "CountryID": 160, "DOB": 27722, "DOB_ISO": "1975-11-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jose]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fontestad]", "PhotoDay": 45340, "StaffID": 174 }, { "AgeType": null, "CountryID": 186, "DOB": 29053, "DOB_ISO": "1979-07-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Marc]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Giannone]", "PhotoDay": 45340, "StaffID": 175 }, { "AgeType": null, "CountryID": 79, "DOB": 28629, "DOB_ISO": "1978-05-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alessandro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Brambilla]", "PhotoDay": 45340, "StaffID": 178 }, { "AgeType": null, "CountryID": 57, "DOB": 33238, "DOB_ISO": "1990-12-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Morgan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Trolle]", "PhotoDay": 45340, "StaffID": 179 }, { "AgeType": null, "CountryID": 57, "DOB": 31370, "DOB_ISO": "1985-11-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Joffrey]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Guillemat]", "PhotoDay": 45340, "StaffID": 181 }, { "AgeType": null, "CountryID": 57, "DOB": 30232, "DOB_ISO": "1982-10-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Christophe]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Perrin]", "PhotoDay": 45340, "StaffID": 183 }, { "AgeType": null, "CountryID": 186, "DOB": 35138, "DOB_ISO": "1996-03-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Finn]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_MacPherson]", "PhotoDay": 45340, "StaffID": 184 }, { "AgeType": null, "CountryID": 120, "DOB": 31626, "DOB_ISO": "1986-08-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Remco]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Advocaat]", "PhotoDay": 45340, "StaffID": 187 }, { "AgeType": null, "CountryID": 138, "DOB": 32533, "DOB_ISO": "1989-01-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mihai]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Marinescu]", "PhotoDay": 45340, "StaffID": 191 }, { "AgeType": null, "CountryID": 167, "DOB": 22164, "DOB_ISO": "1960-09-05", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Peter]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fluckiger1]", "PhotoDay": 45340, "StaffID": 193 }, { "AgeType": null, "CountryID": 186, "DOB": 23155, "DOB_ISO": "1963-05-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matt]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Callaghan]", "PhotoDay": 45340, "StaffID": 195 }, { "AgeType": null, "CountryID": 14, "DOB": 37901, "DOB_ISO": "2003-10-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Zane]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Maloney]", "PhotoDay": 45340, "StaffID": 242 }, { "AgeType": null, "CountryID": 187, "DOB": 38483, "DOB_ISO": "2005-05-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Hunter]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Yeany]", "PhotoDay": 45340, "StaffID": 244 }, { "AgeType": null, "CountryID": 160, "DOB": 38516, "DOB_ISO": "2005-06-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pepe]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Marti1]", "PhotoDay": 45340, "StaffID": 245 }, { "AgeType": null, "CountryID": 186, "DOB": 38389, "DOB_ISO": "2005-02-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Zak]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_OSullivan]", "PhotoDay": 45340, "StaffID": 247 }, { "AgeType": null, "CountryID": 7, "DOB": 37768, "DOB_ISO": "2003-05-27", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Franco]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Colapinto]", "PhotoDay": 45340, "StaffID": 248 }, { "AgeType": null, "CountryID": 73, "DOB": 36791, "DOB_ISO": "2000-09-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kush]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Maini]", "PhotoDay": 45340, "StaffID": 252 }, { "AgeType": null, "CountryID": 187, "DOB": 37092, "DOB_ISO": "2001-07-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Brad]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Benavides]", "PhotoDay": 45340, "StaffID": 253 }, { "AgeType": null, "CountryID": 43, "DOB": 33882, "DOB_ISO": "1992-10-05", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kevin]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Magnussen]", "PhotoDay": 45340, "StaffID": 255 }, { "AgeType": 1, "CountryID": 60, "DOB": 25206, "DOB_ISO": "1969-01-03", "FaceIndex": 13, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Michael]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Schumacher]", "PhotoDay": 45340, "StaffID": 258 }, { "AgeType": 1, "CountryID": 160, "DOB": 25988, "DOB_ISO": "1971-02-24", "FaceIndex": 9, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Pedro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_DeLaRosa]", "PhotoDay": 45340, "StaffID": 259 }, { "AgeType": 1, "CountryID": 23, "DOB": 26442, "DOB_ISO": "1972-05-23", "FaceIndex": 15, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Rubens]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Barichello]", "PhotoDay": 45340, "StaffID": 260 }, { "AgeType": null, "CountryID": 186, "DOB": 34965, "DOB_ISO": "1995-09-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jack]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Aitken]", "PhotoDay": 45340, "StaffID": 263 }, { "AgeType": null, "CountryID": 167, "DOB": 32447, "DOB_ISO": "1988-10-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sebastien1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Buemi]", "PhotoDay": 45340, "StaffID": 264 }, { "AgeType": null, "CountryID": 186, "DOB": 38030, "DOB_ISO": "2004-02-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jonny]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Edgar]", "PhotoDay": 45340, "StaffID": 272 }, { "AgeType": null, "CountryID": 23, "DOB": 38274, "DOB_ISO": "2004-10-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gabriel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bortoleto]", "PhotoDay": 45340, "StaffID": 279 }, { "AgeType": null, "CountryID": 79, "DOB": 38431, "DOB_ISO": "2005-03-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Gabriele]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Mini1]", "PhotoDay": 45340, "StaffID": 280 }, { "AgeType": null, "CountryID": 53, "DOB": 38079, "DOB_ISO": "2004-04-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Paul]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Aron]", "PhotoDay": 45340, "StaffID": 281 }, { "AgeType": null, "CountryID": 166, "DOB": 38005, "DOB_ISO": "2004-01-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Dino]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Beganovic]", "PhotoDay": 45340, "StaffID": 282 }, { "AgeType": null, "CountryID": 160, "DOB": 38090, "DOB_ISO": "2004-04-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mari]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Boya]", "PhotoDay": 45340, "StaffID": 283 }, { "AgeType": null, "CountryID": 9, "DOB": 38392, "DOB_ISO": "2005-02-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Christian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Mansell]", "PhotoDay": 45340, "StaffID": 284 }, { "AgeType": null, "CountryID": 108, "DOB": 36286, "DOB_ISO": "1999-05-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pato]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_OWard]", "PhotoDay": 45340, "StaffID": 285 }, { "AgeType": null, "CountryID": 25, "DOB": 39072, "DOB_ISO": "2006-12-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nikola]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Tsolov]", "PhotoDay": 45340, "StaffID": 286 }, { "AgeType": null, "CountryID": 9, "DOB": 37413, "DOB_ISO": "2002-06-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Tommy]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Smith]", "PhotoDay": 45340, "StaffID": 287 }, { "AgeType": null, "CountryID": 60, "DOB": 38274, "DOB_ISO": "2004-10-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Oliver]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Goethe]", "PhotoDay": 45340, "StaffID": 288 }, { "AgeType": null, "CountryID": 79, "DOB": 38324, "DOB_ISO": "2004-12-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Leonardo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fornaroli]", "PhotoDay": 45340, "StaffID": 289 }, { "AgeType": null, "CountryID": 57, "DOB": 31969, "DOB_ISO": "1987-07-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Laurent]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Mekies]", "PhotoDay": 45340, "StaffID": 290 }, { "AgeType": null, "CountryID": 186, "DOB": 24097, "DOB_ISO": "1965-12-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jonathan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wheatley]", "PhotoDay": 45340, "StaffID": 291 }, { "AgeType": null, "CountryID": 186, "DOB": 23572, "DOB_ISO": "1964-07-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ron]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Meadows]", "PhotoDay": 45340, "StaffID": 292 }, { "AgeType": null, "CountryID": 79, "DOB": 30624, "DOB_ISO": "1983-11-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Marco]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Perrone]", "PhotoDay": 45340, "StaffID": 293 }, { "AgeType": null, "CountryID": 186, "DOB": 23980, "DOB_ISO": "1965-08-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Permane]", "PhotoDay": 45340, "StaffID": 295 }, { "AgeType": null, "CountryID": 167, "DOB": 24116, "DOB_ISO": "1966-01-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Beat]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Zehnder]", "PhotoDay": 45340, "StaffID": 299 }, { "AgeType": null, "CountryID": 186, "DOB": 38470, "DOB_ISO": "2005-04-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Oliver]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gray]", "PhotoDay": 45340, "StaffID": 300 }, { "AgeType": null, "CountryID": 36, "DOB": 38453, "DOB_ISO": "2005-04-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sebastian1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Montoya]", "PhotoDay": 45340, "StaffID": 301 }, { "AgeType": null, "CountryID": 9, "DOB": 38610, "DOB_ISO": "2005-09-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Hugh]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Barter]", "PhotoDay": 45340, "StaffID": 302 }, { "AgeType": null, "CountryID": 108, "DOB": 37819, "DOB_ISO": "2003-07-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alejandro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Garcia1]", "PhotoDay": 45340, "StaffID": 303 }, { "AgeType": null, "CountryID": 79, "DOB": 38723, "DOB_ISO": "2006-01-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nikita]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bedrin]", "PhotoDay": 45340, "StaffID": 304 }, { "AgeType": null, "CountryID": 186, "DOB": 38139, "DOB_ISO": "2004-06-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Taylor]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Barnard]", "PhotoDay": 45340, "StaffID": 305 }, { "AgeType": null, "CountryID": 60, "DOB": 36861, "DOB_ISO": "2000-12-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Sophia]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Florsch1]", "PhotoDay": 45340, "StaffID": 306 }, { "AgeType": null, "CountryID": 23, "DOB": 38001, "DOB_ISO": "2004-01-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Roberto]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Faria]", "PhotoDay": 45340, "StaffID": 307 }, { "AgeType": null, "CountryID": 135, "DOB": 37853, "DOB_ISO": "2003-08-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Piotr]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wisnicki1]", "PhotoDay": 45340, "StaffID": 308 }, { "AgeType": null, "CountryID": 186, "DOB": 28906, "DOB_ISO": "1979-02-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matthew]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gaze]", "PhotoDay": 45340, "StaffID": 309 }, { "AgeType": null, "CountryID": 57, "DOB": 30952, "DOB_ISO": "1984-09-27", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Aurelien1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Pallier]", "PhotoDay": 45340, "StaffID": 310 }, { "AgeType": null, "CountryID": 57, "DOB": 28619, "DOB_ISO": "1978-05-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Raphael1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Landreau]", "PhotoDay": 45340, "StaffID": 311 }, { "AgeType": null, "CountryID": 160, "DOB": 31749, "DOB_ISO": "1986-12-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Cristian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_SolsonaTena]", "PhotoDay": 45340, "StaffID": 312 }, { "AgeType": null, "CountryID": 120, "DOB": 28930, "DOB_ISO": "1979-03-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Johan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_VanderVen]", "PhotoDay": 45340, "StaffID": 313 }, { "AgeType": null, "CountryID": 160, "DOB": 28774, "DOB_ISO": "1978-10-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Enrique]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Colomina]", "PhotoDay": 45340, "StaffID": 314 }, { "AgeType": null, "CountryID": 186, "DOB": 33778, "DOB_ISO": "1992-06-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Clive]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hatton]", "PhotoDay": 45340, "StaffID": 315 }, { "AgeType": null, "CountryID": 186, "DOB": 24483, "DOB_ISO": "1967-01-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jeremy]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cotterill]", "PhotoDay": 45340, "StaffID": 316 }, { "AgeType": null, "CountryID": 57, "DOB": 30149, "DOB_ISO": "1982-07-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Guillaume]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Capietto]", "PhotoDay": 45340, "StaffID": 317 }, { "AgeType": null, "CountryID": 79, "DOB": 31158, "DOB_ISO": "1985-04-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Giacomo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ricci]", "PhotoDay": 45340, "StaffID": 318 }, { "AgeType": null, "CountryID": 186, "DOB": 29839, "DOB_ISO": "1981-09-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Paul]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Devlin]", "PhotoDay": 45340, "StaffID": 319 }, { "AgeType": null, "CountryID": 186, "DOB": 30368, "DOB_ISO": "1983-02-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Marcus]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Dudley]", "PhotoDay": 45340, "StaffID": 320 }, { "AgeType": null, "CountryID": 186, "DOB": 24613, "DOB_ISO": "1967-05-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mark]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Slade]", "PhotoDay": 45340, "StaffID": 321 }, { "AgeType": null, "CountryID": 186, "DOB": 37287, "DOB_ISO": "2002-01-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Luke]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Browning]", "PhotoDay": 45340, "StaffID": 322 }, { "AgeType": null, "CountryID": 57, "DOB": 24986, "DOB_ISO": "1968-05-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Frederic]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vasseur]", "PhotoDay": 45340, "StaffID": 323 }, { "AgeType": null, "CountryID": 79, "DOB": 25986, "DOB_ISO": "1971-02-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andrea]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stella]", "PhotoDay": 45340, "StaffID": 324 }, { "AgeType": null, "CountryID": 186, "DOB": 26984, "DOB_ISO": "1973-11-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Christian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Horner]", "PhotoDay": 45340, "StaffID": 325 }, { "AgeType": null, "CountryID": 10, "DOB": 26310, "DOB_ISO": "1972-01-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Toto]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wolff]", "PhotoDay": 45340, "StaffID": 326 }, { "AgeType": null, "CountryID": 138, "DOB": 23602, "DOB_ISO": "1964-08-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Otmar]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Szafnauer]", "PhotoDay": 45340, "StaffID": 327 }, { "AgeType": null, "CountryID": 186, "DOB": 29026, "DOB_ISO": "1979-06-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_James]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vowles]", "PhotoDay": 45340, "StaffID": 328 }, { "AgeType": null, "CountryID": 79, "DOB": 23839, "DOB_ISO": "1965-04-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Guenther]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Steiner]", "PhotoDay": 45340, "StaffID": 329 }, { "AgeType": null, "CountryID": 79, "DOB": 27356, "DOB_ISO": "1974-11-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alessandro]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_AlunniBravi]", "PhotoDay": 45340, "StaffID": 331 }, { "AgeType": null, "CountryID": 97, "DOB": 26376, "DOB_ISO": "1972-03-18", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mike]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Krack]", "PhotoDay": 45340, "StaffID": 332 }, { "AgeType": null, "CountryID": 186, "DOB": 25029, "DOB_ISO": "1968-07-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_James]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Allison]", "PhotoDay": 45340, "StaffID": 333 }, { "AgeType": null, "CountryID": 186, "DOB": 26312, "DOB_ISO": "1972-01-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_James]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Key]", "PhotoDay": 45340, "StaffID": 334 }, { "AgeType": null, "CountryID": 186, "DOB": 27009, "DOB_ISO": "1973-12-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Dan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fallows]", "PhotoDay": 45340, "StaffID": 335 }, { "AgeType": null, "CountryID": 186, "DOB": 25294, "DOB_ISO": "1969-04-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Peter]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Prodromou]", "PhotoDay": 45340, "StaffID": 337 }, { "AgeType": null, "CountryID": 79, "DOB": 26165, "DOB_ISO": "1971-08-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Daniele]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Rossi]", "PhotoDay": 45340, "StaffID": 338 }, { "AgeType": null, "CountryID": 57, "DOB": 23116, "DOB_ISO": "1963-04-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Bruno]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Corbe]", "PhotoDay": 45340, "StaffID": 341 }, { "AgeType": null, "CountryID": 57, "DOB": 27640, "DOB_ISO": "1975-09-03", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jean-Francois1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Levere]", "PhotoDay": 45340, "StaffID": 343 }, { "AgeType": null, "CountryID": 186, "DOB": 33918, "DOB_ISO": "1992-11-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Neil]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hoddinott]", "PhotoDay": 45340, "StaffID": 344 }, { "AgeType": null, "CountryID": 160, "DOB": 33089, "DOB_ISO": "1990-08-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jose1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Perez1]", "PhotoDay": 45340, "StaffID": 345 }, { "AgeType": null, "CountryID": 79, "DOB": 31131, "DOB_ISO": "1985-03-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Daniel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Chindamo]", "PhotoDay": 45340, "StaffID": 361 }, { "AgeType": null, "CountryID": 57, "DOB": 35697, "DOB_ISO": "1997-09-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mickael1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Costa]", "PhotoDay": 45340, "StaffID": 364 }, { "AgeType": null, "CountryID": 186, "DOB": 28289, "DOB_ISO": "1977-06-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Neil]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Houldey]", "PhotoDay": 45340, "StaffID": 365 }, { "AgeType": null, "CountryID": 186, "DOB": 31752, "DOB_ISO": "1986-12-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Randeep]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Singh]", "PhotoDay": 45340, "StaffID": 366 }, { "AgeType": null, "CountryID": 186, "DOB": 24572, "DOB_ISO": "1967-04-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andy]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stevenson]", "PhotoDay": 45340, "StaffID": 367 }, { "AgeType": null, "CountryID": 186, "DOB": 25361, "DOB_ISO": "1969-06-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Toby]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Brown]", "PhotoDay": 45340, "StaffID": 368 }, { "AgeType": null, "CountryID": 186, "DOB": 29689, "DOB_ISO": "1981-04-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Adam]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Kenyon]", "PhotoDay": 45340, "StaffID": 369 }, { "AgeType": null, "CountryID": 16, "DOB": 26431, "DOB_ISO": "1972-05-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sven]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Smeets]", "PhotoDay": 45340, "StaffID": 370 }, { "AgeType": 0, "CountryID": 186, "DOB": 42691, "DOB_ISO": "2016-11-17", "FaceIndex": 26, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Custom|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Team|]", "PhotoDay": 45340, "StaffID": 372 }, { "AgeType": null, "CountryID": 186, "DOB": 39302, "DOB_ISO": "2007-08-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Arvid]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Lindblad]", "PhotoDay": 45340, "StaffID": 373 }, { "AgeType": null, "CountryID": 57, "DOB": 38131, "DOB_ISO": "2004-05-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Sami]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Meguetounif]", "PhotoDay": 45340, "StaffID": 374 }, { "AgeType": null, "CountryID": 126, "DOB": 38750, "DOB_ISO": "2006-02-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Martinius]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stenshorne]", "PhotoDay": 45340, "StaffID": 375 }, { "AgeType": null, "CountryID": 79, "DOB": 38954, "DOB_ISO": "2006-08-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_AndreaKimi]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Antonelli]", "PhotoDay": 45340, "StaffID": 376 }, { "AgeType": null, "CountryID": 82, "DOB": 36382, "DOB_ISO": "1999-08-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ritomo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Miyata]", "PhotoDay": 45340, "StaffID": 377 }, { "AgeType": null, "CountryID": 132, "DOB": 37921, "DOB_ISO": "2003-10-27", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Joshua]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Durksen]", "PhotoDay": 45340, "StaffID": 378 }, { "AgeType": null, "CountryID": 60, "DOB": 38307, "DOB_ISO": "2004-11-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Tim]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Tramnitz]", "PhotoDay": 45340, "StaffID": 379 }, { "AgeType": null, "CountryID": 108, "DOB": 38342, "DOB_ISO": "2004-12-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Noel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Leon]", "PhotoDay": 45340, "StaffID": 380 }, { "AgeType": null, "CountryID": 120, "DOB": 38601, "DOB_ISO": "2005-09-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Laurens]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_vanHoepen]", "PhotoDay": 45340, "StaffID": 381 }, { "AgeType": null, "CountryID": 10, "DOB": 38688, "DOB_ISO": "2005-12-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Charlie]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Wurz]", "PhotoDay": 45340, "StaffID": 382 }, { "AgeType": null, "CountryID": 108, "DOB": 38012, "DOB_ISO": "2004-01-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Santiago]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ramos]", "PhotoDay": 45340, "StaffID": 383 }, { "AgeType": null, "CountryID": 186, "DOB": 38782, "DOB_ISO": "2006-03-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Callum]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Voisin]", "PhotoDay": 45340, "StaffID": 384 }, { "AgeType": null, "CountryID": 186, "DOB": 38418, "DOB_ISO": "2005-03-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Cian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Shields]", "PhotoDay": 45340, "StaffID": 385 }, { "AgeType": null, "CountryID": 167, "DOB": 38334, "DOB_ISO": "2004-12-13", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Joshua]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Dufek]", "PhotoDay": 45340, "StaffID": 386 }, { "AgeType": null, "CountryID": 135, "DOB": 38746, "DOB_ISO": "2006-01-29", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kacper]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Sztuka]", "PhotoDay": 45340, "StaffID": 387 }, { "AgeType": null, "CountryID": 133, "DOB": 37872, "DOB_ISO": "2003-09-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matias1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Zagazeta]", "PhotoDay": 45340, "StaffID": 388 }, { "AgeType": null, "CountryID": 186, "DOB": 38459, "DOB_ISO": "2005-04-17", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Joseph]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Loake]", "PhotoDay": 45340, "StaffID": 389 }, { "AgeType": null, "CountryID": 187, "DOB": 37538, "DOB_ISO": "2002-10-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Maxwell]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Esterson]", "PhotoDay": 45340, "StaffID": 390 }, { "AgeType": null, "CountryID": 186, "DOB": 23535, "DOB_ISO": "1964-06-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Pat]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Fry]", "PhotoDay": 45340, "StaffID": 391 }, { "AgeType": null, "CountryID": 79, "DOB": 27545, "DOB_ISO": "1975-05-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Diego]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ioverno]", "PhotoDay": 45340, "StaffID": 392 }, { "AgeType": null, "CountryID": 79, "DOB": 30102, "DOB_ISO": "1982-05-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andrea]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Benisi]", "PhotoDay": 45340, "StaffID": 393 }, { "AgeType": null, "CountryID": 171, "DOB": 38670, "DOB_ISO": "2005-11-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Tasanapol]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Inthraphuvasak]", "PhotoDay": 45340, "StaffID": 394 }, { "AgeType": null, "CountryID": 186, "DOB": 33052, "DOB_ISO": "1990-06-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andrew]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Vizard]", "PhotoDay": 45340, "StaffID": 395 }, { "AgeType": null, "CountryID": 57, "DOB": 29715, "DOB_ISO": "1981-05-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Julian]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Rouse]", "PhotoDay": 45340, "StaffID": 397 }, { "AgeType": null, "CountryID": 82, "DOB": 34400, "DOB_ISO": "1994-03-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ryo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hirakawa]", "PhotoDay": 45340, "StaffID": 398 }, { "AgeType": null, "CountryID": 77, "DOB": 38667, "DOB_ISO": "2005-11-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alexander]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Dunne]", "PhotoDay": 45340, "StaffID": 399 }, { "AgeType": null, "CountryID": 167, "DOB": 35620, "DOB_ISO": "1997-07-09", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Lena1]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Buhler]", "PhotoDay": 45340, "StaffID": 400 }, { "AgeType": null, "CountryID": 60, "DOB": 36052, "DOB_ISO": "1998-09-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Carrie]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Schreiner]", "PhotoDay": 45340, "StaffID": 401 }, { "AgeType": null, "CountryID": 187, "DOB": 38115, "DOB_ISO": "2004-05-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Chloe]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Chambers]", "PhotoDay": 45340, "StaffID": 402 }, { "AgeType": null, "CountryID": 79, "DOB": 27266, "DOB_ISO": "1974-08-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Andrea]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_DeZordo]", "PhotoDay": 45340, "StaffID": 403 }, { "AgeType": null, "CountryID": 79, "DOB": 28919, "DOB_ISO": "1979-03-05", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Davide]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Paganelli]", "PhotoDay": 45340, "StaffID": 404 }, { "AgeType": null, "CountryID": 186, "DOB": 37701, "DOB_ISO": "2003-03-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Abbi]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Pulling]", "PhotoDay": 45340, "StaffID": 405 }, { "AgeType": null, "CountryID": 79, "DOB": 39234, "DOB_ISO": "2007-06-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nicola]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Lacorte]", "PhotoDay": 45340, "StaffID": 406 }, { "AgeType": null, "CountryID": 82, "DOB": 39397, "DOB_ISO": "2007-11-11", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kean]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_NakamuraBerta]", "PhotoDay": 45340, "StaffID": 407 }, { "AgeType": null, "CountryID": 186, "DOB": 34852, "DOB_ISO": "1995-06-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Jessica]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hawkins]", "PhotoDay": 45340, "StaffID": 408 }, { "AgeType": null, "CountryID": 167, "DOB": 38939, "DOB_ISO": "2006-08-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Tina]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hausmann]", "PhotoDay": 45340, "StaffID": 409 }, { "AgeType": null, "CountryID": 134, "DOB": 38596, "DOB_ISO": "2005-09-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Bianca]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Bustamante]", "PhotoDay": 45340, "StaffID": 410 }, { "AgeType": null, "CountryID": 187, "DOB": 39145, "DOB_ISO": "2007-03-04", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ugo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ugochukwu]", "PhotoDay": 45340, "StaffID": 411 }, { "AgeType": null, "CountryID": 82, "DOB": 27787, "DOB_ISO": "1976-01-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Ayao]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Komatsu]", "PhotoDay": 45340, "StaffID": 412 }, { "AgeType": null, "CountryID": 78, "DOB": 36419, "DOB_ISO": "1999-09-16", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Robert]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Shwartzman]", "PhotoDay": 45340, "StaffID": 413 }, { "AgeType": null, "CountryID": 186, "DOB": 29124, "DOB_ISO": "1979-09-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Guru]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Johl]", "PhotoDay": 45340, "StaffID": 414 }, { "AgeType": null, "CountryID": 186, "DOB": 31252, "DOB_ISO": "1985-07-24", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_John]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Howard]", "PhotoDay": 45340, "StaffID": 415 }, { "AgeType": null, "CountryID": 186, "DOB": 37865, "DOB_ISO": "2003-09-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_James]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Hedley]", "PhotoDay": 45340, "StaffID": 416 }, { "AgeType": null, "CountryID": 152, "DOB": 39423, "DOB_ISO": "2007-12-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Kabir]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Anurag]", "PhotoDay": 45340, "StaffID": 417 }, { "AgeType": null, "CountryID": 120, "DOB": 38139, "DOB_ISO": "2004-06-01", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Maya]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Weug]", "PhotoDay": 45340, "StaffID": 418 }, { "AgeType": null, "CountryID": 23, "DOB": 39089, "DOB_ISO": "2007-01-07", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Aurelia]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Nobels]", "PhotoDay": 45340, "StaffID": 419 }, { "AgeType": null, "CountryID": 57, "DOB": 35363, "DOB_ISO": "1996-10-25", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Clement1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Dufau]", "PhotoDay": 45340, "StaffID": 420 }, { "AgeType": null, "CountryID": 57, "DOB": 35033, "DOB_ISO": "1995-11-30", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Mathieu]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Meyronne]", "PhotoDay": 45340, "StaffID": 421 }, { "AgeType": null, "CountryID": 160, "DOB": 33806, "DOB_ISO": "1992-07-21", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Carles]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Martinez1]", "PhotoDay": 45340, "StaffID": 422 }, { "AgeType": null, "CountryID": 79, "DOB": 33930, "DOB_ISO": "1992-11-22", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Carlo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Ruffo]", "PhotoDay": 45340, "StaffID": 423 }, { "AgeType": null, "CountryID": 79, "DOB": 33623, "DOB_ISO": "1992-01-20", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Nicola]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Corrias]", "PhotoDay": 45340, "StaffID": 424 }, { "AgeType": null, "CountryID": 57, "DOB": 34938, "DOB_ISO": "1995-08-27", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Arthur]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Rencker]", "PhotoDay": 45340, "StaffID": 425 }, { "AgeType": null, "CountryID": 57, "DOB": 34073, "DOB_ISO": "1993-04-14", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Alexis]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Cigrand]", "PhotoDay": 45340, "StaffID": 426 }, { "AgeType": null, "CountryID": 57, "DOB": 34491, "DOB_ISO": "1994-06-06", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Benjamin]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Constans]", "PhotoDay": 45340, "StaffID": 427 }, { "AgeType": null, "CountryID": 57, "DOB": 34753, "DOB_ISO": "1995-02-23", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Antoine]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Broquin]", "PhotoDay": 45340, "StaffID": 428 }, { "AgeType": null, "CountryID": 160, "DOB": 33613, "DOB_ISO": "1992-01-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Oier]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_BanuelosArteagoitia1]", "PhotoDay": 45340, "StaffID": 429 }, { "AgeType": null, "CountryID": 120, "DOB": 33709, "DOB_ISO": "1992-04-15", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Leo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_vanderEijk]", "PhotoDay": 45340, "StaffID": 430 }, { "AgeType": null, "CountryID": 57, "DOB": 35783, "DOB_ISO": "1997-12-19", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Benjamin]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Renard]", "PhotoDay": 45340, "StaffID": 431 }, { "AgeType": null, "CountryID": 160, "DOB": 31899, "DOB_ISO": "1987-05-02", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Jaume]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Jimenez]", "PhotoDay": 45340, "StaffID": 432 }, { "AgeType": null, "CountryID": 160, "DOB": 26035, "DOB_ISO": "1971-04-12", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Eduardo]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gimenez]", "PhotoDay": 45340, "StaffID": 433 }, { "AgeType": null, "CountryID": 60, "DOB": 34424, "DOB_ISO": "1994-03-31", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matthias]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Gruener]", "PhotoDay": 45340, "StaffID": 434 }, { "AgeType": null, "CountryID": 186, "DOB": 35132, "DOB_ISO": "1996-03-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Matthew]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Stratton]", "PhotoDay": 45340, "StaffID": 435 }, { "AgeType": null, "CountryID": 185, "DOB": 36613, "DOB_ISO": "2000-03-28", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Amna]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_AlQubaisi]", "PhotoDay": 45340, "StaffID": 436 }, { "AgeType": null, "CountryID": 185, "DOB": 37476, "DOB_ISO": "2002-08-08", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Hamda]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_AlQubaisi]", "PhotoDay": 45340, "StaffID": 437 }, { "AgeType": null, "CountryID": 120, "DOB": 37662, "DOB_ISO": "2003-02-10", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Female_Emely]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_deHeus]", "PhotoDay": 45340, "StaffID": 438 }, { "AgeType": null, "CountryID": 56, "DOB": 39016, "DOB_ISO": "2006-10-26", "FaceIndex": null, "FaceType": null, "FirstName": "[StaffName_Forename_Male_Tuukka]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[StaffName_Surname_Taponen]", "PhotoDay": 45340, "StaffID": 439 }, { "AgeType": 1, "CountryID": 120, "DOB": 28847, "DOB_ISO": "1970-01-01", "FaceIndex": 11, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Kurt]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Gunther1]", "PhotoDay": 45340, "StaffID": 536 }, { "AgeType": 1, "CountryID": 30, "DOB": 31132, "DOB_ISO": "1970-01-01", "FaceIndex": 6, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Elizabeth]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Daisley]", "PhotoDay": 45340, "StaffID": 537 }, { "AgeType": 1, "CountryID": 167, "DOB": 29858, "DOB_ISO": "1970-01-01", "FaceIndex": 13, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Yves]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Blanchard]", "PhotoDay": 45340, "StaffID": 538 }, { "AgeType": 1, "CountryID": 60, "DOB": 30218, "DOB_ISO": "1970-01-01", "FaceIndex": 14, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Holger]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Horn]", "PhotoDay": 45340, "StaffID": 539 }, { "AgeType": 1, "CountryID": 56, "DOB": 31319, "DOB_ISO": "1970-01-01", "FaceIndex": 0, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Minna]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Skoglund]", "PhotoDay": 45340, "StaffID": 540 }, { "AgeType": 1, "CountryID": 9, "DOB": 30271, "DOB_ISO": "1970-01-01", "FaceIndex": 16, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Joshua]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Hubbard]", "PhotoDay": 45340, "StaffID": 541 }, { "AgeType": 1, "CountryID": 120, "DOB": 29545, "DOB_ISO": "1970-01-01", "FaceIndex": 17, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Wilfried]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Keller]", "PhotoDay": 45340, "StaffID": 542 }, { "AgeType": 1, "CountryID": 73, "DOB": 31674, "DOB_ISO": "1970-01-01", "FaceIndex": 3, "FaceType": 3, "FirstName": "[StaffName_Forename_Female_Oporajita]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Ki]", "PhotoDay": 45340, "StaffID": 543 }, { "AgeType": 1, "CountryID": 157, "DOB": 29977, "DOB_ISO": "1970-01-01", "FaceIndex": 19, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Brandon]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Hume]", "PhotoDay": 45340, "StaffID": 544 }, { "AgeType": 1, "CountryID": 16, "DOB": 31621, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Isabelle]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Arnaud]", "PhotoDay": 45340, "StaffID": 545 }, { "AgeType": 1, "CountryID": 97, "DOB": 31979, "DOB_ISO": "1970-01-01", "FaceIndex": 7, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Gisela]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Vogt]", "PhotoDay": 45340, "StaffID": 546 }, { "AgeType": 0, "CountryID": 126, "DOB": 38128, "DOB_ISO": "1970-01-01", "FaceIndex": 22, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Jonas]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Eklund]", "PhotoDay": 45340, "StaffID": 547 }, { "AgeType": 0, "CountryID": 187, "DOB": 38163, "DOB_ISO": "1970-01-01", "FaceIndex": 23, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Varden]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Robin]", "PhotoDay": 45340, "StaffID": 548 }, { "AgeType": 0, "CountryID": 14, "DOB": 37895, "DOB_ISO": "1970-01-01", "FaceIndex": 24, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Justin]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Beech]", "PhotoDay": 45340, "StaffID": 549 }, { "AgeType": 0, "CountryID": 120, "DOB": 37859, "DOB_ISO": "1970-01-01", "FaceIndex": 25, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Stefan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Graf]", "PhotoDay": 45340, "StaffID": 550 }, { "AgeType": 0, "CountryID": 180, "DOB": 38658, "DOB_ISO": "1970-01-01", "FaceIndex": 1, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Aadilah]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Masih]", "PhotoDay": 45340, "StaffID": 551 }, { "AgeType": 0, "CountryID": 57, "DOB": 38532, "DOB_ISO": "1970-01-01", "FaceIndex": 4, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Isabelle]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Carpentier]", "PhotoDay": 45340, "StaffID": 554 }, { "AgeType": 1, "CountryID": 160, "DOB": 32859, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Mario]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Calvo]", "PhotoDay": 45340, "StaffID": 555 }, { "AgeType": 1, "CountryID": 82, "DOB": 32435, "DOB_ISO": "1970-01-01", "FaceIndex": 6, "FaceType": 4, "FirstName": "[StaffName_Forename_Male_Toshio]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Kimura]", "PhotoDay": 45340, "StaffID": 556 }, { "AgeType": 1, "CountryID": 167, "DOB": 32508, "DOB_ISO": "1970-01-01", "FaceIndex": 8, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Jeanne]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Dupont]", "PhotoDay": 45340, "StaffID": 557 }, { "AgeType": 1, "CountryID": 77, "DOB": 30987, "DOB_ISO": "1970-01-01", "FaceIndex": 33, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Mick]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Irvine]", "PhotoDay": 45340, "StaffID": 558 }, { "AgeType": 0, "CountryID": 186, "DOB": 34618, "DOB_ISO": "1970-01-01", "FaceIndex": 9, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Gladys]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Waghorn]", "PhotoDay": 45340, "StaffID": 559 }, { "AgeType": 1, "CountryID": 23, "DOB": 33806, "DOB_ISO": "1970-01-01", "FaceIndex": 0, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Micaela]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Almeida]", "PhotoDay": 45340, "StaffID": 560 }, { "AgeType": 1, "CountryID": 7, "DOB": 31081, "DOB_ISO": "1970-01-01", "FaceIndex": 10, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Agustin1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Sanhueza]", "PhotoDay": 45340, "StaffID": 561 }, { "AgeType": 1, "CountryID": 167, "DOB": 32986, "DOB_ISO": "1970-01-01", "FaceIndex": 11, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Marc]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Lucas]", "PhotoDay": 45340, "StaffID": 562 }, { "AgeType": 1, "CountryID": 82, "DOB": 34192, "DOB_ISO": "1970-01-01", "FaceIndex": 3, "FaceType": 4, "FirstName": "[StaffName_Forename_Female_Sachiko]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Ito]", "PhotoDay": 45340, "StaffID": 563 }, { "AgeType": 1, "CountryID": 186, "DOB": 30877, "DOB_ISO": "1970-01-01", "FaceIndex": 14, "FaceType": 4, "FirstName": "[StaffName_Forename_Male_Kimi]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Griffiths]", "PhotoDay": 45340, "StaffID": 564 }, { "AgeType": 0, "CountryID": 57, "DOB": 34735, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Karine]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Mercier]", "PhotoDay": 45340, "StaffID": 565 }, { "AgeType": 1, "CountryID": 120, "DOB": 33512, "DOB_ISO": "1970-01-01", "FaceIndex": 7, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Elisabeth]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Neumann]", "PhotoDay": 45340, "StaffID": 566 }, { "AgeType": 1, "CountryID": 56, "DOB": 30663, "DOB_ISO": "1970-01-01", "FaceIndex": 8, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Ulla]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Salo]", "PhotoDay": 45340, "StaffID": 567 }, { "AgeType": 1, "CountryID": 57, "DOB": 29232, "DOB_ISO": "1970-01-01", "FaceIndex": 9, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_William]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Marchand]", "PhotoDay": 45340, "StaffID": 568 }, { "AgeType": 1, "CountryID": 186, "DOB": 38553, "DOB_ISO": "2005-07-20", "FaceIndex": 4, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ella|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Lloyd|]", "PhotoDay": 45340, "StaffID": 600 }, { "AgeType": 1, "CountryID": 186, "DOB": 37879, "DOB_ISO": "2003-09-15", "FaceIndex": 1, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|John|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Bennett|]", "PhotoDay": 45340, "StaffID": 601 }, { "AgeType": 1, "CountryID": 79, "DOB": 38975, "DOB_ISO": "2006-09-15", "FaceIndex": 2, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Brando|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Badoer|]", "PhotoDay": 45340, "StaffID": 602 }, { "AgeType": 0, "CountryID": 79, "DOB": 35205, "DOB_ISO": "1996-05-20", "FaceIndex": 0, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Antonio|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Fuoco|]", "PhotoDay": 45340, "StaffID": 603 }, { "AgeType": 0, "CountryID": 23, "DOB": 38477, "DOB_ISO": "2005-05-05", "FaceIndex": 1, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Rafael|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Câmara|]", "PhotoDay": 45340, "StaffID": 604 }, { "AgeType": 0, "CountryID": 57, "DOB": 37992, "DOB_ISO": "2004-01-06", "FaceIndex": 0, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Doriane|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Pin|]", "PhotoDay": 45340, "StaffID": 605 }, { "AgeType": 0, "CountryID": 120, "DOB": 37894, "DOB_ISO": "2003-09-30", "FaceIndex": 1, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Nina|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Gademan|]", "PhotoDay": 45340, "StaffID": 606 }, { "AgeType": 0, "CountryID": 187, "DOB": 36957, "DOB_ISO": "2001-03-07", "FaceIndex": 2, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Courtney|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Crone|]", "PhotoDay": 45340, "StaffID": 607 }, { "AgeType": 0, "CountryID": 186, "DOB": 35935, "DOB_ISO": "1998-05-20", "FaceIndex": 3, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Jamie|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Chadwick|]", "PhotoDay": 45340, "StaffID": 608 }, { "AgeType": 0, "CountryID": 187, "DOB": 38991, "DOB_ISO": "2006-10-01", "FaceIndex": 4, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Lia|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Block|]", "PhotoDay": 45340, "StaffID": 609 }, { "AgeType": 0, "CountryID": 43, "DOB": 39292, "DOB_ISO": "2007-07-29", "FaceIndex": 2, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Noah|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Strømsted|]", "PhotoDay": 45340, "StaffID": 610 }, { "AgeType": 0, "CountryID": 9, "DOB": 38906, "DOB_ISO": "2006-07-08", "FaceIndex": 3, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|James|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Wharton|]", "PhotoDay": 45340, "StaffID": 611 }, { "AgeType": 0, "CountryID": 35, "DOB": 38984, "DOB_ISO": "2006-09-24", "FaceIndex": 4, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Gerrard|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Xie|]", "PhotoDay": 45340, "StaffID": 612 }, { "AgeType": 0, "CountryID": 160, "DOB": 38888, "DOB_ISO": "2006-06-20", "FaceIndex": 5, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Bruno|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|del Pino|]", "PhotoDay": 45340, "StaffID": 613 }, { "AgeType": 0, "CountryID": 57, "DOB": 38970, "DOB_ISO": "2006-09-10", "FaceIndex": 6, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Alessandro|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Giusti|]", "PhotoDay": 45340, "StaffID": 614 }, { "AgeType": 0, "CountryID": 57, "DOB": 39255, "DOB_ISO": "2007-06-22", "FaceIndex": 7, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Théophile|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Naël|]", "PhotoDay": 45340, "StaffID": 615 }, { "AgeType": 0, "CountryID": 136, "DOB": 38842, "DOB_ISO": "2006-05-05", "FaceIndex": 8, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ivan|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Domingues|]", "PhotoDay": 45340, "StaffID": 616 }, { "AgeType": 0, "CountryID": 121, "DOB": 39213, "DOB_ISO": "2007-05-11", "FaceIndex": 9, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Louis|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Sharp|]", "PhotoDay": 45340, "StaffID": 617 }, { "AgeType": 0, "CountryID": 135, "DOB": 38050, "DOB_ISO": "2004-03-04", "FaceIndex": 10, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Roman|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Bilinski|]", "PhotoDay": 45340, "StaffID": 618 }, { "AgeType": 0, "CountryID": 160, "DOB": 37998, "DOB_ISO": "2004-01-12", "FaceIndex": 11, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Javier|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Sagrera|]", "PhotoDay": 45340, "StaffID": 619 }, { "AgeType": 0, "CountryID": 79, "DOB": 37755, "DOB_ISO": "2003-05-14", "FaceIndex": 12, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Nicola|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Marinangeli|]", "PhotoDay": 45340, "StaffID": 620 }, { "AgeType": 0, "CountryID": 152, "DOB": 39021, "DOB_ISO": "2006-10-31", "FaceIndex": 13, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Christian|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Ho|]", "PhotoDay": 45340, "StaffID": 621 }, { "AgeType": 1, "CountryID": 79, "DOB": 29221, "DOB_ISO": "1980-01-01", "FaceIndex": 14, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Bryan|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Bozzi|]", "PhotoDay": 45340, "StaffID": 622 }, { "AgeType": 1, "CountryID": 186, "DOB": 33055, "DOB_ISO": "1990-07-01", "FaceIndex": 13, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Richard|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Wood|]", "PhotoDay": 45340, "StaffID": 623 }, { "AgeType": 1, "CountryID": 60, "DOB": 33420, "DOB_ISO": "1991-07-01", "FaceIndex": 4, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Laura|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Mueller|]", "PhotoDay": 45340, "StaffID": 624 }, { "AgeType": 1, "CountryID": 186, "DOB": 32325, "DOB_ISO": "1988-07-01", "FaceIndex": 12, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ronan|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|O'Hare|]", "PhotoDay": 45340, "StaffID": 625 }, { "AgeType": 1, "CountryID": 79, "DOB": 31594, "DOB_ISO": "1986-07-01", "FaceIndex": 11, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ernesto|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Desiderio|]", "PhotoDay": 45340, "StaffID": 626 }, { "AgeType": 1, "CountryID": 186, "DOB": 24942, "DOB_ISO": "1968-04-14", "FaceIndex": 19, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Rob|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Marshall|]", "PhotoDay": 45340, "StaffID": 628 }, { "AgeType": 1, "CountryID": 57, "DOB": 29251, "DOB_ISO": "1980-01-31", "FaceIndex": 4, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|David|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Sanchez|]", "PhotoDay": 45340, "StaffID": 630 }, { "AgeType": 1, "CountryID": 186, "DOB": 28518, "DOB_ISO": "1978-01-28", "FaceIndex": 5, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Selin|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Tur|]", "PhotoDay": 45340, "StaffID": 633 }, { "AgeType": 1, "CountryID": 186, "DOB": 23025, "DOB_ISO": "1963-01-14", "FaceIndex": 22, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Sean|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Whitehead|]", "PhotoDay": 45340, "StaffID": 635 }, { "AgeType": 1, "CountryID": 186, "DOB": 30864, "DOB_ISO": "1984-07-01", "FaceIndex": 24, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Mark|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Robinson|]", "PhotoDay": 45340, "StaffID": 636 }, { "AgeType": 1, "CountryID": 186, "DOB": 27942, "DOB_ISO": "1976-07-01", "FaceIndex": 23, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Peter|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Machin|]", "PhotoDay": 45340, "StaffID": 637 }, { "AgeType": 1, "CountryID": 186, "DOB": 29037, "DOB_ISO": "1979-07-01", "FaceIndex": 18, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Will|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Courtenay|]", "PhotoDay": 45340, "StaffID": 638 }, { "AgeType": 1, "CountryID": 186, "DOB": 29037, "DOB_ISO": "1979-07-01", "FaceIndex": 25, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Richard|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Wolverson|]", "PhotoDay": 45340, "StaffID": 639 }, { "AgeType": null, "CountryID": 187, "DOB": 30498, "DOB_ISO": "1983-07-01", "FaceIndex": null, "FaceType": null, "FirstName": "[STRING_LITERAL:Value=|Mark|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Lowe|]", "PhotoDay": 45340, "StaffID": 640 }, { "AgeType": 1, "CountryID": 160, "DOB": 28708, "DOB_ISO": "1978-08-06", "FaceIndex": 17, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ignacio|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Rueda|]", "PhotoDay": 45340, "StaffID": 641 }, { "AgeType": 1, "CountryID": 57, "DOB": 26388, "DOB_ISO": "1972-03-30", "FaceIndex": 3, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Loïc|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Serra|]", "PhotoDay": 45340, "StaffID": 642 }, { "AgeType": 1, "CountryID": 187, "DOB": 33420, "DOB_ISO": "1991-07-01", "FaceIndex": 10, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Steven|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Petrik|]", "PhotoDay": 45340, "StaffID": 643 }, { "AgeType": 1, "CountryID": 160, "DOB": 33055, "DOB_ISO": "1990-07-01", "FaceIndex": 9, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Jose Manuel|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Lopez|]", "PhotoDay": 45340, "StaffID": 644 }, { "AgeType": 0, "CountryID": 187, "DOB": 36615, "DOB_ISO": "2000-03-30", "FaceIndex": 14, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Colton|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Herta|]", "PhotoDay": 45340, "StaffID": 645 }, { "AgeType": 0, "CountryID": 23, "DOB": 39148, "DOB_ISO": "2007-03-07", "FaceIndex": 15, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Emerson|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Fittipaldi Jr.|]", "PhotoDay": 45340, "StaffID": 646 }, { "AgeType": 0, "CountryID": 7, "DOB": 36866, "DOB_ISO": "2000-12-11", "FaceIndex": 16, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Nicolás|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Varrone|]", "PhotoDay": 45340, "StaffID": 647 }, { "AgeType": 0, "CountryID": 108, "DOB": 39783, "DOB_ISO": "2008-12-01", "FaceIndex": 17, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ernesto|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Rivera|]", "PhotoDay": 45340, "StaffID": 648 }, { "AgeType": 0, "CountryID": 186, "DOB": 39669, "DOB_ISO": "2008-08-09", "FaceIndex": 18, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Freddie|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Slater|]", "PhotoDay": 45340, "StaffID": 649 }, { "AgeType": 0, "CountryID": 79, "DOB": 39394, "DOB_ISO": "2007-11-07", "FaceIndex": 19, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Matteo|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|De Palo|]", "PhotoDay": 45340, "StaffID": 650 }, { "AgeType": 0, "CountryID": 79, "DOB": 39655, "DOB_ISO": "2008-07-26", "FaceIndex": 20, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Mattia|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Colnaghi|]", "PhotoDay": 45340, "StaffID": 651 }, { "AgeType": 0, "CountryID": 82, "DOB": 39411, "DOB_ISO": "2007-11-24", "FaceIndex": 21, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Taito|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Kato|]", "PhotoDay": 45340, "StaffID": 652 }, { "AgeType": 0, "CountryID": 135, "DOB": 39566, "DOB_ISO": "2008-05-28", "FaceIndex": 22, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Maciej|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Gładysz|]", "PhotoDay": 45340, "StaffID": 653 }, { "AgeType": 0, "CountryID": 82, "DOB": 39267, "DOB_ISO": "2007-07-03", "FaceIndex": 23, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Kanato|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Le|]", "PhotoDay": 45340, "StaffID": 654 }, { "AgeType": 0, "CountryID": 82, "DOB": 39003, "DOB_ISO": "2006-10-12", "FaceIndex": 24, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Hiyu|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Yamakoshi|]", "PhotoDay": 45340, "StaffID": 655 }, { "AgeType": 0, "CountryID": 57, "DOB": 39555, "DOB_ISO": "2008-05-17", "FaceIndex": 25, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Enzo|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Deligny|]", "PhotoDay": 45340, "StaffID": 656 }, { "AgeType": 0, "CountryID": 23, "DOB": 39104, "DOB_ISO": "2007-01-22", "FaceIndex": 26, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Pedro|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Clerot|]", "PhotoDay": 45340, "StaffID": 657 }, { "AgeType": 0, "CountryID": 108, "DOB": 38285, "DOB_ISO": "2004-10-30", "FaceIndex": 27, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|José|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Garfias|]", "PhotoDay": 45340, "StaffID": 658 }, { "AgeType": 0, "CountryID": 158, "DOB": 38222, "DOB_ISO": "2004-08-28", "FaceIndex": 28, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Michael|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Shin|]", "PhotoDay": 45340, "StaffID": 659 }, { "AgeType": 0, "CountryID": 77, "DOB": 39384, "DOB_ISO": "2007-10-28", "FaceIndex": 29, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Fionn|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|McLaughlin|]", "PhotoDay": 45340, "StaffID": 660 }, { "AgeType": 0, "CountryID": 82, "DOB": 38749, "DOB_ISO": "2006-01-01", "FaceIndex": 30, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Jin|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Nakamura|]", "PhotoDay": 45340, "StaffID": 661 }, { "AgeType": 0, "CountryID": 161, "DOB": 39274, "DOB_ISO": "2007-07-10", "FaceIndex": 31, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Yevan|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|David|]", "PhotoDay": 45340, "StaffID": 662 }, { "AgeType": 0, "CountryID": 23, "DOB": 38607, "DOB_ISO": "2005-09-12", "FaceIndex": 32, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Fernando|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Barrichello|]", "PhotoDay": 45340, "StaffID": 663 }, { "AgeType": 0, "CountryID": 171, "DOB": 38875, "DOB_ISO": "2006-06-06", "FaceIndex": 33, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Nandhavud|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Bhirombhakdi|]", "PhotoDay": 45340, "StaffID": 664 }, { "AgeType": 1, "CountryID": 60, "DOB": 39661, "DOB_ISO": "2008-08-01", "FaceIndex": 3, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Mathilda|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Paatz|]", "PhotoDay": 45340, "StaffID": 665 }, { "AgeType": 0, "CountryID": 187, "DOB": 40181, "DOB_ISO": "2009-01-03", "FaceIndex": 5, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Kaylee|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Countryman|]", "PhotoDay": 45340, "StaffID": 666 }, { "AgeType": 1, "CountryID": 56, "DOB": 36800, "DOB_ISO": "2000-10-06", "FaceIndex": 0, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Kalle|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Rovanperä|]", "PhotoDay": 45340, "StaffID": 667 }, { "AgeType": 0, "CountryID": 186, "DOB": 38970, "DOB_ISO": "2006-08-10", "FaceIndex": 6, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Ella|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Stevens|]", "PhotoDay": 45340, "StaffID": 668 }, { "AgeType": 0, "CountryID": 57, "DOB": 40148, "DOB_ISO": "2008-12-01", "FaceIndex": 7, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Jade|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Jacquet|]", "PhotoDay": 45340, "StaffID": 669 }, { "AgeType": 0, "CountryID": 187, "DOB": 39892, "DOB_ISO": "2009-03-20", "FaceIndex": 8, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Payton|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Westcott|]", "PhotoDay": 45340, "StaffID": 670 }, { "AgeType": 0, "CountryID": 43, "DOB": 39794, "DOB_ISO": "2008-12-12", "FaceIndex": 9, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Alba|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Hurup Larsen|]", "PhotoDay": 45340, "StaffID": 671 }, { "AgeType": 1, "CountryID": 10, "DOB": 39109, "DOB_ISO": "2007-01-27", "FaceIndex": 0, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Emma|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Felbermayr|]", "PhotoDay": 45340, "StaffID": 672 }, { "AgeType": 1, "CountryID": 23, "DOB": 38460, "DOB_ISO": "2005-03-19", "FaceIndex": 1, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Rafaela|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Ferreira|]", "PhotoDay": 45340, "StaffID": 673 }, { "AgeType": 1, "CountryID": 186, "DOB": 38981, "DOB_ISO": "2006-08-21", "FaceIndex": 2, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Alisha|]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[STRING_LITERAL:Value=|Palmowski|]", "PhotoDay": 45340, "StaffID": 674 }, { "AgeType": null, "CountryID": 186, "DOB": 21545, "DOB_ISO": "1958-12-26", "FaceIndex": null, "FaceType": null, "FirstName": "[STRING_LITERAL:Value=|Adrian|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Newey|]", "PhotoDay": 45340, "StaffID": 675 }, { "AgeType": null, "CountryID": 79, "DOB": 18365, "DOB_ISO": "1950-04-12", "FaceIndex": null, "FaceType": null, "FirstName": "[STRING_LITERAL:Value=|Flavio|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Briatore|]", "PhotoDay": 45340, "StaffID": 676 }, { "AgeType": 1, "CountryID": 186, "DOB": 26673, "DOB_ISO": "1973-01-09", "FaceIndex": 5, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Jon|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Tomlinson|]", "PhotoDay": 45340, "StaffID": 677 }, { "AgeType": 1, "CountryID": 186, "DOB": 33055, "DOB_ISO": "1990-07-01", "FaceIndex": 16, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Stephen|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Knowles|]", "PhotoDay": 45340, "StaffID": 678 }, { "AgeType": 1, "CountryID": 186, "DOB": 28547, "DOB_ISO": "1978-02-26", "FaceIndex": 15, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Marc|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Hynes|]", "PhotoDay": 45340, "StaffID": 679 }, { "AgeType": 1, "CountryID": 57, "DOB": 34516, "DOB_ISO": "1994-07-01", "FaceIndex": 8, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Cédric|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Michel-Grosjean|]", "PhotoDay": 45340, "StaffID": 680 }, { "AgeType": 1, "CountryID": 186, "DOB": 25284, "DOB_ISO": "1969-03-22", "FaceIndex": 20, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Nick|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Chester|]", "PhotoDay": 45340, "StaffID": 681 }, { "AgeType": 1, "CountryID": 79, "DOB": 32690, "DOB_ISO": "1989-07-01", "FaceIndex": 7, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Carlo|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Pasetti|]", "PhotoDay": 45340, "StaffID": 682 }, { "AgeType": 1, "CountryID": 186, "DOB": 23070, "DOB_ISO": "1963-02-28", "FaceIndex": 21, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Tim|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Goss|]", "PhotoDay": 45340, "StaffID": 683 }, { "AgeType": 1, "CountryID": 186, "DOB": 31959, "DOB_ISO": "1987-07-01", "FaceIndex": 6, "FaceType": 0, "FirstName": "[STRING_LITERAL:Value=|Stuart|]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 0, "LastName": "[STRING_LITERAL:Value=|Barlow|]", "PhotoDay": 45340, "StaffID": 684 }, { "AgeType": 0, "CountryID": 160, "DOB": 38407, "DOB_ISO": "1970-01-01", "FaceIndex": 10, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Mario]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Ruiz]", "PhotoDay": 46023, "StaffID": 685 }, { "AgeType": 0, "CountryID": 186, "DOB": 39301, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Felicity]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Rivers]", "PhotoDay": 46023, "StaffID": 686 }, { "AgeType": 0, "CountryID": 79, "DOB": 39080, "DOB_ISO": "1970-01-01", "FaceIndex": 7, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Antonietta]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Tani]", "PhotoDay": 46023, "StaffID": 687 }, { "AgeType": 0, "CountryID": 9, "DOB": 39330, "DOB_ISO": "1970-01-01", "FaceIndex": 7, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Megan]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Thomas]", "PhotoDay": 46023, "StaffID": 688 }, { "AgeType": 0, "CountryID": 187, "DOB": 38830, "DOB_ISO": "1970-01-01", "FaceIndex": 31, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Tyson]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Stott]", "PhotoDay": 46023, "StaffID": 689 }, { "AgeType": 1, "CountryID": 10, "DOB": 32758, "DOB_ISO": "1970-01-01", "FaceIndex": 25, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Ludwig]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Hartmann]", "PhotoDay": 46023, "StaffID": 690 }, { "AgeType": 1, "CountryID": 7, "DOB": 32318, "DOB_ISO": "1970-01-01", "FaceIndex": 17, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Ramon]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Farias]", "PhotoDay": 46023, "StaffID": 691 }, { "AgeType": 1, "CountryID": 121, "DOB": 31634, "DOB_ISO": "1970-01-01", "FaceIndex": 17, "FaceType": 1, "FirstName": "[StaffName_Forename_Male_Brandon]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_ODeasunaigh1]", "PhotoDay": 46023, "StaffID": 692 }, { "AgeType": 1, "CountryID": 60, "DOB": 33329, "DOB_ISO": "1970-01-01", "FaceIndex": 28, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Stephan]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Wenzel]", "PhotoDay": 46023, "StaffID": 693 }, { "AgeType": 0, "CountryID": 186, "DOB": 35758, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Livia]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Feeney]", "PhotoDay": 46023, "StaffID": 694 }, { "AgeType": 0, "CountryID": 82, "DOB": 36609, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 4, "FirstName": "[StaffName_Forename_Female_Mieko]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Imai]", "PhotoDay": 46023, "StaffID": 695 }, { "AgeType": 1, "CountryID": 137, "DOB": 32719, "DOB_ISO": "1970-01-01", "FaceIndex": 6, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Rahima]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Baz]", "PhotoDay": 46023, "StaffID": 696 }, { "AgeType": 1, "CountryID": 30, "DOB": 28950, "DOB_ISO": "1970-01-01", "FaceIndex": 32, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Charles]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Biddiscombe]", "PhotoDay": 46023, "StaffID": 697 }, { "AgeType": 1, "CountryID": 77, "DOB": 31596, "DOB_ISO": "1970-01-01", "FaceIndex": 1, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Barbara]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Beltran]", "PhotoDay": 46023, "StaffID": 698 }, { "AgeType": 1, "CountryID": 187, "DOB": 29133, "DOB_ISO": "1970-01-01", "FaceIndex": 34, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Benaim]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Prior]", "PhotoDay": 46023, "StaffID": 699 }, { "AgeType": 1, "CountryID": 186, "DOB": 32972, "DOB_ISO": "1970-01-01", "FaceIndex": 2, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Felicity]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Beltran]", "PhotoDay": 46023, "StaffID": 700 }, { "AgeType": 0, "CountryID": 23, "DOB": 35347, "DOB_ISO": "1970-01-01", "FaceIndex": 2, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Estefania1]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Gomes]", "PhotoDay": 46023, "StaffID": 701 }, { "AgeType": 0, "CountryID": 167, "DOB": 35160, "DOB_ISO": "1970-01-01", "FaceIndex": 1, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Karine]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Bertin]", "PhotoDay": 46023, "StaffID": 702 }, { "AgeType": 1, "CountryID": 82, "DOB": 32955, "DOB_ISO": "1970-01-01", "FaceIndex": 4, "FaceType": 4, "FirstName": "[StaffName_Forename_Female_Hiroko]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Sakurai]", "PhotoDay": 46023, "StaffID": 703 }, { "AgeType": 1, "CountryID": 23, "DOB": 32621, "DOB_ISO": "1970-01-01", "FaceIndex": 4, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Florencia1]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Neves]", "PhotoDay": 46023, "StaffID": 704 }, { "AgeType": 1, "CountryID": 30, "DOB": 29178, "DOB_ISO": "1970-01-01", "FaceIndex": 24, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Lando]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Randall]", "PhotoDay": 46023, "StaffID": 705 }, { "AgeType": 1, "CountryID": 180, "DOB": 34751, "DOB_ISO": "1970-01-01", "FaceIndex": 7, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Nawaal]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Asker]", "PhotoDay": 46023, "StaffID": 706 }, { "AgeType": 1, "CountryID": 23, "DOB": 31393, "DOB_ISO": "1970-01-01", "FaceIndex": 8, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Beatriz]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Henriques]", "PhotoDay": 46023, "StaffID": 707 }, { "AgeType": 1, "CountryID": 7, "DOB": 32290, "DOB_ISO": "1970-01-01", "FaceIndex": 8, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Claudio1]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Zuniga]", "PhotoDay": 46023, "StaffID": 708 }, { "AgeType": 1, "CountryID": 167, "DOB": 33228, "DOB_ISO": "1970-01-01", "FaceIndex": 3, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Madeleine]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Dumas]", "PhotoDay": 46023, "StaffID": 709 }, { "AgeType": 0, "CountryID": 136, "DOB": 35938, "DOB_ISO": "1970-01-01", "FaceIndex": 0, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Florencia1]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Guerreiro]", "PhotoDay": 46023, "StaffID": 710 }, { "AgeType": 0, "CountryID": 23, "DOB": 38942, "DOB_ISO": "1970-01-01", "FaceIndex": 6, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Antonia1]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Lopes]", "PhotoDay": 46023, "StaffID": 711 }, { "AgeType": 0, "CountryID": 9, "DOB": 38521, "DOB_ISO": "1970-01-01", "FaceIndex": 14, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Emmanuel]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Mulford]", "PhotoDay": 46023, "StaffID": 712 }, { "AgeType": 0, "CountryID": 60, "DOB": 38575, "DOB_ISO": "1970-01-01", "FaceIndex": 17, "FaceType": 0, "FirstName": "[StaffName_Forename_Male_Friedrich]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Fischer]", "PhotoDay": 46023, "StaffID": 713 }, { "AgeType": 0, "CountryID": 57, "DOB": 38431, "DOB_ISO": "1970-01-01", "FaceIndex": 14, "FaceType": 2, "FirstName": "[StaffName_Forename_Male_Rui]", "Gender": 0, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Olivier]", "PhotoDay": 46023, "StaffID": 714 }, { "AgeType": 0, "CountryID": 167, "DOB": 39366, "DOB_ISO": "1970-01-01", "FaceIndex": 8, "FaceType": 2, "FirstName": "[StaffName_Forename_Female_Nathalie]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Francois]", "PhotoDay": 46023, "StaffID": 715 }, { "AgeType": 0, "CountryID": 60, "DOB": 39122, "DOB_ISO": "1970-01-01", "FaceIndex": 5, "FaceType": 0, "FirstName": "[StaffName_Forename_Female_Ute]", "Gender": 1, "IsGeneratedForCustomTeam": 0, "IsGeneratedStaff": 1, "LastName": "[StaffName_Surname_Hubner1]", "PhotoDay": 46023, "StaffID": 716 } ], "Staff_Contracts": [ { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 70000000, "StaffID": 1, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 34000000, "StaffID": 2, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 8000000, "StaffID": 3, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 3600000, "StaffID": 4, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 5000000, "StaffID": 8, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 65000000, "StaffID": 10, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 10000000, "StaffID": 11, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 20000000, "StaffID": 12, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 7000000, "StaffID": 14, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 12000000, "StaffID": 15, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 8000000, "StaffID": 17, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 12000000, "StaffID": 18, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1800000, "StaffID": 20, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1500000, "StaffID": 22, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 15000000, "StaffID": 23, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 4410000, "StaffID": 26, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 4400000, "StaffID": 28, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 4650000, "StaffID": 37, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 3480000, "StaffID": 38, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1650000, "StaffID": 40, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2860000, "StaffID": 43, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2810000, "StaffID": 44, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1130000, "StaffID": 47, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1230000, "StaffID": 48, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1560000, "StaffID": 50, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1530000, "StaffID": 51, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 920000, "StaffID": 54, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 830000, "StaffID": 55, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 750000, "StaffID": 56, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 730000, "StaffID": 58, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 850000, "StaffID": 59, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 20000000, "StaffID": 77, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 900000, "StaffID": 80, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2000000, "StaffID": 81, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 7000000, "StaffID": 83, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 650000, "StaffID": 88, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1000000, "StaffID": 95, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 99, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 13000000, "StaffID": 102, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1600000, "StaffID": 105, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1400000, "StaffID": 107, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 900000, "StaffID": 120, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 850000, "StaffID": 121, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 123, "StartDay": 46022, "StartingBonus": 0, "TeamID": 20 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 550000, "StaffID": 127, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 132, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 800000, "StaffID": 135, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 600000, "StaffID": 140, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1000000, "StaffID": 142, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 5000000, "StaffID": 144, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2000000, "StaffID": 248, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 350000, "StaffID": 252, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 252, "StartDay": 46022, "StartingBonus": 0, "TeamID": 15 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 253, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1600000, "StaffID": 264, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2000000, "StaffID": 279, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 280, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 280, "StartDay": 46022, "StartingBonus": 0, "TeamID": 16 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 750000, "StaffID": 281, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 282, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 282, "StartDay": 46022, "StartingBonus": 0, "TeamID": 18 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 450000, "StaffID": 283, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 283, "StartDay": 46022, "StartingBonus": 0, "TeamID": 11 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1900000, "StaffID": 285, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 286, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 286, "StartDay": 46022, "StartingBonus": 0, "TeamID": 19 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 288, "StartDay": 46022, "StartingBonus": 0, "TeamID": 16 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 600000, "StaffID": 289, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1450000, "StaffID": 292, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 900000, "StaffID": 293, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 301, "StartDay": 46022, "StartingBonus": 0, "TeamID": 11 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 440000, "StaffID": 309, "StartDay": 46022, "StartingBonus": 0, "TeamID": 13 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 470000, "StaffID": 310, "StartDay": 46022, "StartingBonus": 0, "TeamID": 15 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 490000, "StaffID": 311, "StartDay": 46022, "StartingBonus": 0, "TeamID": 18 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 300000, "StaffID": 312, "StartDay": 46022, "StartingBonus": 0, "TeamID": 17 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 410000, "StaffID": 313, "StartDay": 46022, "StartingBonus": 0, "TeamID": 20 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 314, "StartDay": 46022, "StartingBonus": 0, "TeamID": 19 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 315, "StartDay": 46022, "StartingBonus": 0, "TeamID": 14 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 465000, "StaffID": 316, "StartDay": 46022, "StartingBonus": 0, "TeamID": 16 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 450000, "StaffID": 317, "StartDay": 46022, "StartingBonus": 0, "TeamID": 11 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 380000, "StaffID": 318, "StartDay": 46022, "StartingBonus": 0, "TeamID": 21 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 319, "StartDay": 46022, "StartingBonus": 0, "TeamID": 12 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1360000, "StaffID": 320, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 700000, "StaffID": 322, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 3600000, "StaffID": 333, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2550000, "StaffID": 334, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 4070000, "StaffID": 337, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 3000000, "StaffID": 365, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1100000, "StaffID": 367, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2250000, "StaffID": 369, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 950000, "StaffID": 370, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 750000, "StaffID": 373, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 375, "StartDay": 46022, "StartingBonus": 0, "TeamID": 13 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2000000, "StaffID": 376, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 450000, "StaffID": 377, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 377, "StartDay": 46022, "StartingBonus": 0, "TeamID": 14 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 378, "StartDay": 46022, "StartingBonus": 0, "TeamID": 12 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 380, "StartDay": 46022, "StartingBonus": 0, "TeamID": 19 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 381, "StartDay": 46022, "StartingBonus": 0, "TeamID": 21 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 385, "StartDay": 46022, "StartingBonus": 0, "TeamID": 17 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2650000, "StaffID": 391, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1550000, "StaffID": 392, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 394, "StartDay": 46022, "StartingBonus": 0, "TeamID": 15 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 950000, "StaffID": 395, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1050000, "StaffID": 397, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 650000, "StaffID": 398, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 700000, "StaffID": 399, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 399, "StartDay": 46022, "StartingBonus": 0, "TeamID": 13 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2300000, "StaffID": 403, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2350000, "StaffID": 404, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 406, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 411, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2660000, "StaffID": 414, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1000000, "StaffID": 415, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 8, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 300000, "StaffID": 439, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 439, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 8, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 150000, "StaffID": 600, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 601, "StartDay": 46022, "StartingBonus": 0, "TeamID": 21 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 602, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1000000, "StaffID": 603, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 604, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 604, "StartDay": 46022, "StartingBonus": 0, "TeamID": 12 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 606, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 610, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 611, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 612, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 613, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 614, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 614, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 615, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 617, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 618, "StartDay": 46022, "StartingBonus": 0, "TeamID": 18 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 621, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1450000, "StaffID": 622, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1100000, "StaffID": 623, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 900000, "StaffID": 624, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 800000, "StaffID": 625, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 850000, "StaffID": 626, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1700000, "StaffID": 630, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1500000, "StaffID": 638, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 800000, "StaffID": 640, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 950000, "StaffID": 641, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 4100000, "StaffID": 642, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 820000, "StaffID": 643, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 800000, "StaffID": 644, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1700000, "StaffID": 645, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 645, "StartDay": 46022, "StartingBonus": 0, "TeamID": 14 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 646, "StartDay": 46022, "StartingBonus": 0, "TeamID": 17 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 647, "StartDay": 46022, "StartingBonus": 0, "TeamID": 20 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 648, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 648, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 400000, "StaffID": 649, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 649, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 650, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 650, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 651, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 651, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 300000, "StaffID": 652, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 652, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 653, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 654, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 655, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 656, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 657, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 658, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 659, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 8, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 660, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 660, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 661, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 661, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 662, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 663, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 664, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 665, "StartDay": 46022, "StartingBonus": 0, "TeamID": 10 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 8, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 150000, "StaffID": 666, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 750000, "StaffID": 667, "StartDay": 46022, "StartingBonus": 0, "TeamID": 7 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 9, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 668, "StartDay": 46022, "StartingBonus": 0, "TeamID": 2 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 669, "StartDay": 46022, "StartingBonus": 0, "TeamID": 6 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 9, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 671, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 672, "StartDay": 46022, "StartingBonus": 0, "TeamID": 9 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 150000, "StaffID": 673, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 9, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 674, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2150000, "StaffID": 677, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2028, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1850000, "StaffID": 678, "StartDay": 46022, "StartingBonus": 0, "TeamID": 3 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1200000, "StaffID": 679, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1150000, "StaffID": 680, "StartDay": 46022, "StartingBonus": 0, "TeamID": 1 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2050000, "StaffID": 681, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 1050000, "StaffID": 682, "StartDay": 46022, "StartingBonus": 0, "TeamID": 32 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 2250000, "StaffID": 683, "StartDay": 46022, "StartingBonus": 0, "TeamID": 8 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 900000, "StaffID": 684, "StartDay": 46022, "StartingBonus": 0, "TeamID": 5 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 4, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 750000, "StaffID": 87, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 5, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 450000, "StaffID": 378, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2026, "PosInTeam": 6, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 150000, "StaffID": 605, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2027, "PosInTeam": 7, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 670, "StartDay": 46022, "StartingBonus": 0, "TeamID": 4 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 450000, "StaffID": 169, "StartDay": 46022, "StartingBonus": 0, "TeamID": 11 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 395000, "StaffID": 156, "StartDay": 46022, "StartingBonus": 0, "TeamID": 11 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 159, "StartDay": 46022, "StartingBonus": 0, "TeamID": 12 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 460000, "StaffID": 158, "StartDay": 46022, "StartingBonus": 0, "TeamID": 12 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 440000, "StaffID": 148, "StartDay": 46022, "StartingBonus": 0, "TeamID": 13 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 385000, "StaffID": 435, "StartDay": 46022, "StartingBonus": 0, "TeamID": 13 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 500000, "StaffID": 161, "StartDay": 46022, "StartingBonus": 0, "TeamID": 14 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 460000, "StaffID": 160, "StartDay": 46022, "StartingBonus": 0, "TeamID": 14 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 470000, "StaffID": 179, "StartDay": 46022, "StartingBonus": 0, "TeamID": 15 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 420000, "StaffID": 426, "StartDay": 46022, "StartingBonus": 0, "TeamID": 15 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 415000, "StaffID": 153, "StartDay": 46022, "StartingBonus": 0, "TeamID": 16 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 470000, "StaffID": 152, "StartDay": 46022, "StartingBonus": 0, "TeamID": 16 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 330000, "StaffID": 149, "StartDay": 46022, "StartingBonus": 0, "TeamID": 17 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 250000, "StaffID": 341, "StartDay": 46022, "StartingBonus": 0, "TeamID": 17 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 495000, "StaffID": 151, "StartDay": 46022, "StartingBonus": 0, "TeamID": 18 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 440000, "StaffID": 429, "StartDay": 46022, "StartingBonus": 0, "TeamID": 18 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 445000, "StaffID": 174, "StartDay": 46022, "StartingBonus": 0, "TeamID": 19 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 495000, "StaffID": 422, "StartDay": 46022, "StartingBonus": 0, "TeamID": 19 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 360000, "StaffID": 421, "StartDay": 46022, "StartingBonus": 0, "TeamID": 20 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 415000, "StaffID": 175, "StartDay": 46022, "StartingBonus": 0, "TeamID": 20 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 395000, "StaffID": 146, "StartDay": 46022, "StartingBonus": 0, "TeamID": 21 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 340000, "StaffID": 424, "StartDay": 46022, "StartingBonus": 0, "TeamID": 21 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 285000, "StaffID": 427, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 285000, "StaffID": 343, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 235000, "StaffID": 361, "StartDay": 46022, "StartingBonus": 0, "TeamID": 22 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 260000, "StaffID": 172, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 275000, "StaffID": 173, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 300000, "StaffID": 178, "StartDay": 46022, "StartingBonus": 0, "TeamID": 23 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 265000, "StaffID": 181, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 230000, "StaffID": 431, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 215000, "StaffID": 433, "StartDay": 46022, "StartingBonus": 0, "TeamID": 24 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 220000, "StaffID": 184, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 364, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 255000, "StaffID": 183, "StartDay": 46022, "StartingBonus": 0, "TeamID": 25 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 205000, "StaffID": 423, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 180000, "StaffID": 187, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 150000, "StaffID": 165, "StartDay": 46022, "StartingBonus": 0, "TeamID": 26 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 166, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 245000, "StaffID": 428, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 190000, "StaffID": 338, "StartDay": 46022, "StartingBonus": 0, "TeamID": 27 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 275000, "StaffID": 163, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 235000, "StaffID": 162, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 225000, "StaffID": 345, "StartDay": 46022, "StartingBonus": 0, "TeamID": 28 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 220000, "StaffID": 344, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 200000, "StaffID": 195, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 160000, "StaffID": 555, "StartDay": 46022, "StartingBonus": 0, "TeamID": 29 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 185000, "StaffID": 145, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 135000, "StaffID": 420, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 160000, "StaffID": 193, "StartDay": 46022, "StartingBonus": 0, "TeamID": 30 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 1, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 100000, "StaffID": 432, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 2, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 130000, "StaffID": 191, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 }, { "AffiliateDualRoleClause": 0, "BreakoutClause": 0.5, "ContractType": 0, "EndSeason": 2030, "PosInTeam": 3, "RaceBonus": 0, "RaceBonusTargetPos": 1, "Salary": 160000, "StaffID": 425, "StartDay": 46022, "StartingBonus": 0, "TeamID": 31 } ], "Staff_DriverData": [ { "Aggression": 59, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Ham]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 2, "LastKnownDriverNumber": 44, "Marketability": 100, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 1, "TargetMarketability": 100, "WantsChampionDriverNumber": 0 }, { "Aggression": 69, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Lec]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 26, "LastKnownDriverNumber": 16, "Marketability": 92, "MarketabilityProgress": 26, "PerformanceEvaluationDay": null, "StaffID": 2, "TargetMarketability": 98, "WantsChampionDriverNumber": 1 }, { "Aggression": 60, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Alb]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 20, "LastKnownDriverNumber": 23, "Marketability": 68, "MarketabilityProgress": 44, "PerformanceEvaluationDay": null, "StaffID": 3, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 52, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Bot]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 5, "LastKnownDriverNumber": 77, "Marketability": 75, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 8, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 59, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Vet]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 9, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 86, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Ver]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 22, "LastKnownDriverNumber": 3, "Marketability": 95, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 10, "TargetMarketability": 95, "WantsChampionDriverNumber": 1 }, { "Aggression": 64, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Sai]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 12, "LastKnownDriverNumber": 55, "Marketability": 85, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 11, "TargetMarketability": 85, "WantsChampionDriverNumber": 1 }, { "Aggression": 59, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Nor]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 30, "LastKnownDriverNumber": 4, "Marketability": 88, "MarketabilityProgress": 34, "PerformanceEvaluationDay": null, "StaffID": 12, "TargetMarketability": 95, "WantsChampionDriverNumber": 1 }, { "Aggression": 59, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ric]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 84, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 13, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "StaffID": 14, "Improvability": 18, "Aggression": 75, "DriverCode": "[DriverCode_Oco]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": 31, "AssignedCarNumber": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 60, "TargetMarketability": 65, "MarketabilityProgress": 22, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 66, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Gas]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 18, "LastKnownDriverNumber": 10, "Marketability": 70, "MarketabilityProgress": 24, "PerformanceEvaluationDay": null, "StaffID": 15, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 58, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Per]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 6, "LastKnownDriverNumber": 11, "Marketability": 80, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 17, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "Aggression": 57, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Str]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 22, "LastKnownDriverNumber": 18, "Marketability": 55, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 18, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 55, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Gio]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 16, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 44, "PerformanceEvaluationDay": null, "StaffID": 20, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 48, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Van]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 12, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": -8, "PerformanceEvaluationDay": null, "StaffID": 22, "TargetMarketability": 62, "WantsChampionDriverNumber": 1 }, { "Aggression": 68, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Rus]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 28, "LastKnownDriverNumber": 63, "Marketability": 80, "MarketabilityProgress": 34, "PerformanceEvaluationDay": null, "StaffID": 23, "TargetMarketability": 90, "WantsChampionDriverNumber": 1 }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Msc]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 40, "LastKnownDriverNumber": null, "Marketability": 68, "MarketabilityProgress": 52, "PerformanceEvaluationDay": null, "StaffID": 74, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Dev]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 18, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 76, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 71, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Alo]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 1, "LastKnownDriverNumber": 14, "Marketability": 85, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 77, "TargetMarketability": 85, "WantsChampionDriverNumber": 1 }, { "Aggression": 93, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Nis]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 78, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Pfi]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 22, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 68, "PerformanceEvaluationDay": null, "StaffID": 80, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 73, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tsu]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 50, "LastKnownDriverNumber": 22, "Marketability": 70, "MarketabilityProgress": 52, "PerformanceEvaluationDay": null, "StaffID": 81, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 43, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Kub]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 5, "LastKnownDriverNumber": null, "Marketability": 70, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 82, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 54, "AssignedCarNumber": 1, "DriverCode": "[DriverCode_Hul]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 5, "LastKnownDriverNumber": 27, "Marketability": 70, "MarketabilityProgress": 20, "PerformanceEvaluationDay": null, "StaffID": 83, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 54, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Pou]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 87, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 52, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bos]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 20, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 88, "TargetMarketability": 45, "WantsChampionDriverNumber": 1 }, { "Aggression": 61, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Dar]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 91, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "StaffID": 95, "Improvability": 70, "Aggression": 72, "DriverCode": "[DriverCode_Law]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": 30, "AssignedCarNumber": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 60, "TargetMarketability": 75, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Rve]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 55, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 99, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Pia]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": 1, "Improvability": 60, "LastKnownDriverNumber": 81, "Marketability": 75, "MarketabilityProgress": 60, "PerformanceEvaluationDay": null, "StaffID": 102, "TargetMarketability": 85, "WantsChampionDriverNumber": 1 }, { "Aggression": 53, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Zho]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 30, "LastKnownDriverNumber": 24, "Marketability": 65, "MarketabilityProgress": 60, "PerformanceEvaluationDay": null, "StaffID": 105, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Dru]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": 1, "HasWonF3": null, "Improvability": 50, "LastKnownDriverNumber": null, "Marketability": 63, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 106, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ves]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 60, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 107, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 82, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Jco]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 40, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 109, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 82, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Aco]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 37, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 110, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 82, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Coh]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 114, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 53, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Fre]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 115, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 85, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Sar]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 116, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Efi]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 55, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 117, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 88, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Sta]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 119, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 61, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Iwa]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 60, "LastKnownDriverNumber": null, "Marketability": 66, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 120, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 64, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Cra]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 121, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Vil]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 55, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 123, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 59, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Vma]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": 1, "Improvability": 60, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 127, "TargetMarketability": 78, "WantsChampionDriverNumber": 1 }, { "Aggression": 37, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Cco]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 128, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hau]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": 1, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 130, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 55, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ale]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 38, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -1, "PerformanceEvaluationDay": null, "StaffID": 132, "TargetMarketability": 55, "WantsChampionDriverNumber": 1 }, { "Aggression": 83, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Nov]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 133, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Doo]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 65, "LastKnownDriverNumber": 7, "Marketability": 58, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 135, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 64, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hug]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 18, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 140, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "StaffID": 142, "Improvability": 90, "Aggression": 68, "DriverCode": "[DriverCode_Bea]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": 87, "AssignedCarNumber": 2, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 72, "TargetMarketability": 95, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 74, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Sau]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 143, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "StaffID": 144, "Improvability": 87, "Aggression": 72, "DriverCode": "[DriverCode_Had]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": 6, "AssignedCarNumber": 2, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 62, "TargetMarketability": 85, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mal]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 68, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 242, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 77, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Yea]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 244, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 70, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Jma]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 245, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 61, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Osu]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 247, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 69, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Col]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": 43, "Marketability": 70, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 248, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mai]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 48, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 252, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 72, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ben]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 60, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 253, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 90, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mag]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 14, "LastKnownDriverNumber": 20, "Marketability": 68, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 255, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 50, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Placeholder]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 59, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 258, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 50, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Placeholder]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 259, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 50, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Placeholder]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 260, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ait]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 16, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 263, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bue]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 6, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 23, "PerformanceEvaluationDay": null, "StaffID": 264, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 36, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Edg]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 272, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "StaffID": 279, "Improvability": 90, "Aggression": 62, "DriverCode": "[DriverCode_Bor]", "WantsChampionDriverNumber": null, "LastKnownDriverNumber": 5, "AssignedCarNumber": 2, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": 1, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 65, "TargetMarketability": 90, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Min]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 85, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 280, "TargetMarketability": 85, "WantsChampionDriverNumber": null }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Aro]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 281, "TargetMarketability": 55, "WantsChampionDriverNumber": null }, { "Aggression": 54, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Beg]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 86, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 282, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Boy]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": 1, "Improvability": 84, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 283, "TargetMarketability": 62, "WantsChampionDriverNumber": null }, { "Aggression": 69, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Man]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 284, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 72, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Owa]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 45, "LastKnownDriverNumber": null, "Marketability": 70, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 285, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 72, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tso]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 90, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 286, "TargetMarketability": 90, "WantsChampionDriverNumber": null }, { "Aggression": 87, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Smi]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 41, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 287, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Goe]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 288, "TargetMarketability": 68, "WantsChampionDriverNumber": null }, { "Aggression": 50, "AssignedCarNumber": null, "DriverCode": "[DriverCode_For]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 84, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 289, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 73, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Gra]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 300, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mon]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 84, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 301, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 22, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hba]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 302, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Gar]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 303, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 70, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bed]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 81, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 304, "TargetMarketability": 65, "WantsChampionDriverNumber": null }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bar]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 305, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 69, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Flo]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 306, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 76, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Far]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 5, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 307, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 55, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Wis]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 40, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 308, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bro]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 322, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 70, "AssignedCarNumber": 2, "DriverCode": "[DriverCode_Lin]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 92, "LastKnownDriverNumber": 41, "Marketability": 58, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 373, "TargetMarketability": 90, "WantsChampionDriverNumber": null }, { "Aggression": 69, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Meg]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 38, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 374, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ste]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 88, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 375, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "StaffID": 376, "Improvability": 90, "Aggression": 64, "DriverCode": "[DriverCode_Ant]", "WantsChampionDriverNumber": null, "LastKnownDriverNumber": 12, "AssignedCarNumber": 2, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 75, "TargetMarketability": 95, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Miy]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 40, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 377, "TargetMarketability": 65, "WantsChampionDriverNumber": null }, { "Aggression": 69, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Dur]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 76, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 378, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tra]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 85, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 379, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Leo]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 380, "TargetMarketability": 60, "WantsChampionDriverNumber": null }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Vho]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 381, "TargetMarketability": 65, "WantsChampionDriverNumber": null }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Wur]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 382, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 59, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ram]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 383, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Voi]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 384, "TargetMarketability": 75, "WantsChampionDriverNumber": null }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Shi]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 74, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 385, "TargetMarketability": 50, "WantsChampionDriverNumber": null }, { "Aggression": 70, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Duf]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 41, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 386, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Szt]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 81, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 387, "TargetMarketability": 60, "WantsChampionDriverNumber": null }, { "Aggression": 72, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Zag]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 47, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 388, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 71, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Loa]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 389, "TargetMarketability": 70, "WantsChampionDriverNumber": null }, { "Aggression": 61, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Est]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 40, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 390, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Int]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 394, "TargetMarketability": 60, "WantsChampionDriverNumber": null }, { "Aggression": 44, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hir]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 12, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 398, "TargetMarketability": 55, "WantsChampionDriverNumber": null }, { "StaffID": 399, "Improvability": 90, "Aggression": 84, "DriverCode": "[DriverCode_Dun]", "WantsChampionDriverNumber": null, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 1, "PerformanceEvaluationDay": null, "Marketability": 65, "TargetMarketability": 85, "MarketabilityProgress": -2, "FeederSeriesAssignedCarNumber": 2 }, { "Aggression": 45, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Buh]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 400, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 37, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Sch]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 48, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 401, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Cha]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 402, "TargetMarketability": 74, "WantsChampionDriverNumber": null }, { "Aggression": 59, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Pul]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 67, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 405, "TargetMarketability": 76, "WantsChampionDriverNumber": null }, { "StaffID": 406, "Improvability": 80, "Aggression": 80, "DriverCode": "[DriverCode_Lac]", "WantsChampionDriverNumber": null, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 50, "TargetMarketability": 60, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": 1 }, { "Aggression": 57, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Nak]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 86, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 407, "TargetMarketability": 80, "WantsChampionDriverNumber": null }, { "Aggression": 61, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Haw]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 57, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 408, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Has]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 74, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 409, "TargetMarketability": 66, "WantsChampionDriverNumber": null }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bus]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 74, "LastKnownDriverNumber": null, "Marketability": 64, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 410, "TargetMarketability": 74, "WantsChampionDriverNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ugo]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 92, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 411, "TargetMarketability": 80, "WantsChampionDriverNumber": null }, { "Aggression": 75, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Shw]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 1, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 413, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hed]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 41, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 416, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "StaffID": 417, "Improvability": 85, "Aggression": 55, "DriverCode": "[DriverCode_Anu]", "WantsChampionDriverNumber": null, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 44, "TargetMarketability": 60, "MarketabilityProgress": 70, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Weu]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 66, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 418, "TargetMarketability": 76, "WantsChampionDriverNumber": null }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Nob]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 59, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 419, "TargetMarketability": 69, "WantsChampionDriverNumber": null }, { "Aggression": 24, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Aal]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 436, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 74, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hal]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 437, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Deh]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 50, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 438, "TargetMarketability": 40, "WantsChampionDriverNumber": null }, { "Aggression": 70, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tap]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 90, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 439, "TargetMarketability": 90, "WantsChampionDriverNumber": null }, { "Aggression": 81, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Ekl]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 41, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 547, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Rob]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 46, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 548, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Bee]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 56, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 549, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 76, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Gra]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 46, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 550, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 54, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mas]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 53, "MarketabilityProgress": -2, "PerformanceEvaluationDay": null, "StaffID": 551, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 29, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Car]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 42, "MarketabilityProgress": 70, "PerformanceEvaluationDay": null, "StaffID": 554, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|LLO|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 62, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 600, "TargetMarketability": 71, "WantsChampionDriverNumber": 1 }, { "Aggression": 58, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|BEN|]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 72, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 601, "TargetMarketability": 55, "WantsChampionDriverNumber": 1 }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|BAD|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 80, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 602, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|FUO|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 1, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 22, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 603, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|CAM|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 95, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 604, "TargetMarketability": 95, "WantsChampionDriverNumber": 1 }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|PIN|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 68, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 605, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "Aggression": 60, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|GAD|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 68, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 606, "TargetMarketability": 69, "WantsChampionDriverNumber": 1 }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|CRO|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 55, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 607, "TargetMarketability": 62, "WantsChampionDriverNumber": 1 }, { "Aggression": 52, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|CHA|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 65, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 608, "TargetMarketability": 40, "WantsChampionDriverNumber": 1 }, { "Aggression": 60, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|BLO|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 76, "LastKnownDriverNumber": null, "Marketability": 63, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 609, "TargetMarketability": 73, "WantsChampionDriverNumber": 1 }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|STR|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 90, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 610, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "StaffID": 611, "Improvability": 89, "Aggression": 69, "DriverCode": "[STRING_LITERAL:Value=|WHA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 75, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "StaffID": 612, "Improvability": 82, "Aggression": 68, "DriverCode": "[STRING_LITERAL:Value=|XIE|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 48, "TargetMarketability": 60, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 3 }, { "StaffID": 613, "Improvability": 80, "Aggression": 70, "DriverCode": "[STRING_LITERAL:Value=|DEL|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 50, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 3 }, { "Aggression": 62, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|GIU|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 88, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 614, "TargetMarketability": 85, "WantsChampionDriverNumber": 1 }, { "Aggression": 71, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|NAE|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 86, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 615, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 64, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|DOM|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 75, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 616, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 60, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|SHA|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 86, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 617, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|BIL|]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 618, "TargetMarketability": 65, "WantsChampionDriverNumber": 1 }, { "Aggression": 60, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|SAG|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 62, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 619, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|MAR|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 60, "LastKnownDriverNumber": null, "Marketability": 44, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 620, "TargetMarketability": 55, "WantsChampionDriverNumber": 1 }, { "StaffID": 621, "Improvability": 82, "Aggression": 58, "DriverCode": "[STRING_LITERAL:Value=|HO|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 52, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "StaffID": 645, "Improvability": 82, "Aggression": 71, "DriverCode": "[STRING_LITERAL:Value=|HER|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 75, "TargetMarketability": 85, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "StaffID": 646, "Improvability": 84, "Aggression": 63, "DriverCode": "[STRING_LITERAL:Value=|FIT|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 1 }, { "StaffID": 647, "Improvability": 80, "Aggression": 55, "DriverCode": "[STRING_LITERAL:Value=|VAR|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 55, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 1 }, { "StaffID": 648, "Improvability": 90, "Aggression": 57, "DriverCode": "[STRING_LITERAL:Value=|RIV|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 3 }, { "StaffID": 649, "Improvability": 102, "Aggression": 74, "DriverCode": "[STRING_LITERAL:Value=|SLA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 65, "TargetMarketability": 95, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "Aggression": 55, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|DPA|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 93, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 650, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "Aggression": 52, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|CLN|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 95, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 651, "TargetMarketability": 85, "WantsChampionDriverNumber": 1 }, { "StaffID": 652, "Improvability": 85, "Aggression": 62, "DriverCode": "[STRING_LITERAL:Value=|KAT|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 1 }, { "Aggression": 53, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|GLA|]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 90, "LastKnownDriverNumber": null, "Marketability": 43, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 653, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|LEK|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 86, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 654, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|YAM|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 655, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "Aggression": 73, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|DEL|]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 94, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 656, "TargetMarketability": 80, "WantsChampionDriverNumber": 1 }, { "StaffID": 657, "Improvability": 91, "Aggression": 63, "DriverCode": "[STRING_LITERAL:Value=|CLE|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 52, "TargetMarketability": 70, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 1 }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|GAR|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 65, "LastKnownDriverNumber": null, "Marketability": 43, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 658, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|SHI|]", "FeederSeriesAssignedCarNumber": 1, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 74, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 659, "TargetMarketability": 55, "WantsChampionDriverNumber": 1 }, { "Aggression": 64, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|MCL|]", "FeederSeriesAssignedCarNumber": 2, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 84, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 660, "TargetMarketability": 75, "WantsChampionDriverNumber": 1 }, { "Aggression": 65, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|NAK|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 79, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 661, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "StaffID": 662, "Improvability": 84, "Aggression": 70, "DriverCode": "[STRING_LITERAL:Value=|DAV|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 45, "TargetMarketability": 55, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "Aggression": 66, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|BAR|]", "FeederSeriesAssignedCarNumber": 3, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 663, "TargetMarketability": 70, "WantsChampionDriverNumber": 1 }, { "StaffID": 664, "Improvability": 81, "Aggression": 60, "DriverCode": "[STRING_LITERAL:Value=|BHI|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 43, "TargetMarketability": 50, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": 2 }, { "StaffID": 665, "Improvability": 82, "Aggression": 50, "DriverCode": "[STRING_LITERAL:Value=|PAA|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 57, "TargetMarketability": 62, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 666, "Improvability": 80, "Aggression": 57, "DriverCode": "[STRING_LITERAL:Value=|COU|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 66, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 667, "Improvability": 84, "Aggression": 70, "DriverCode": "[STRING_LITERAL:Value=|ROV|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 75, "TargetMarketability": 80, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 56, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|STE|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 78, "LastKnownDriverNumber": null, "Marketability": 56, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 668, "TargetMarketability": 60, "WantsChampionDriverNumber": 1 }, { "StaffID": 669, "Improvability": 80, "Aggression": 54, "DriverCode": "[STRING_LITERAL:Value=|JAC|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 56, "TargetMarketability": 60, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 670, "Improvability": 90, "Aggression": 60, "DriverCode": "[STRING_LITERAL:Value=|WES|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 55, "TargetMarketability": 66, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 671, "Improvability": 90, "Aggression": 55, "DriverCode": "[STRING_LITERAL:Value=|LAR|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 59, "TargetMarketability": 71, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "StaffID": 672, "Improvability": 84, "Aggression": 64, "DriverCode": "[STRING_LITERAL:Value=|FEL|]", "WantsChampionDriverNumber": 1, "LastKnownDriverNumber": null, "AssignedCarNumber": null, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "HasRacedEnoughToJoinF1": 0, "PerformanceEvaluationDay": null, "Marketability": 58, "TargetMarketability": 69, "MarketabilityProgress": 0, "FeederSeriesAssignedCarNumber": null }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|FER|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 70, "LastKnownDriverNumber": null, "Marketability": 60, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 673, "TargetMarketability": 71, "WantsChampionDriverNumber": 1 }, { "Aggression": 67, "AssignedCarNumber": null, "DriverCode": "[STRING_LITERAL:Value=|PAL|]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 82, "LastKnownDriverNumber": null, "Marketability": 61, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 674, "TargetMarketability": 73, "WantsChampionDriverNumber": 1 }, { "Aggression": 24, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Rui]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 685, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 18, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Riv]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 40, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 686, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 71, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tan]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 51, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 687, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 41, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Tho]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 688, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 81, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Sto]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 45, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 689, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 71, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Lop]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 55, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 711, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 43, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Mul]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 712, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 68, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Fis]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 52, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 713, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 69, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Oli]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 42, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 714, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 24, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Fra]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 51, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 715, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 }, { "Aggression": 63, "AssignedCarNumber": null, "DriverCode": "[DriverCode_Hub]", "FeederSeriesAssignedCarNumber": null, "HasRacedEnoughToJoinF1": 0, "HasSuperLicense": 0, "HasWonF2": null, "HasWonF3": null, "Improvability": 0, "LastKnownDriverNumber": null, "Marketability": 58, "MarketabilityProgress": 0, "PerformanceEvaluationDay": null, "StaffID": 716, "TargetMarketability": 50, "WantsChampionDriverNumber": 1 } ], "Staff_DriverNumbers": [ { "CurrentHolder": null, "Number": 0 }, { "CurrentHolder": 12, "Number": 1 }, { "CurrentHolder": null, "Number": 2 }, { "CurrentHolder": 10, "Number": 3 }, { "CurrentHolder": 12, "Number": 4 }, { "CurrentHolder": 279, "Number": 5 }, { "CurrentHolder": 144, "Number": 6 }, { "CurrentHolder": 135, "Number": 7 }, { "CurrentHolder": null, "Number": 8 }, { "CurrentHolder": null, "Number": 9 }, { "CurrentHolder": 15, "Number": 10 }, { "CurrentHolder": 17, "Number": 11 }, { "CurrentHolder": 376, "Number": 12 }, { "CurrentHolder": null, "Number": 13 }, { "CurrentHolder": 77, "Number": 14 }, { "CurrentHolder": null, "Number": 15 }, { "CurrentHolder": 2, "Number": 16 }, { "CurrentHolder": 18, "Number": 18 }, { "CurrentHolder": null, "Number": 19 }, { "CurrentHolder": 255, "Number": 20 }, { "CurrentHolder": null, "Number": 21 }, { "CurrentHolder": 81, "Number": 22 }, { "CurrentHolder": 3, "Number": 23 }, { "CurrentHolder": 105, "Number": 24 }, { "CurrentHolder": null, "Number": 25 }, { "CurrentHolder": null, "Number": 26 }, { "CurrentHolder": 83, "Number": 27 }, { "CurrentHolder": null, "Number": 28 }, { "CurrentHolder": null, "Number": 29 }, { "CurrentHolder": 95, "Number": 30 }, { "CurrentHolder": 14, "Number": 31 }, { "CurrentHolder": null, "Number": 32 }, { "CurrentHolder": null, "Number": 33 }, { "CurrentHolder": null, "Number": 34 }, { "CurrentHolder": null, "Number": 35 }, { "CurrentHolder": null, "Number": 36 }, { "CurrentHolder": null, "Number": 37 }, { "CurrentHolder": null, "Number": 38 }, { "CurrentHolder": null, "Number": 39 }, { "CurrentHolder": null, "Number": 40 }, { "CurrentHolder": 373, "Number": 41 }, { "CurrentHolder": null, "Number": 42 }, { "CurrentHolder": 248, "Number": 43 }, { "CurrentHolder": 1, "Number": 44 }, { "CurrentHolder": null, "Number": 45 }, { "CurrentHolder": null, "Number": 46 }, { "CurrentHolder": null, "Number": 47 }, { "CurrentHolder": null, "Number": 48 }, { "CurrentHolder": null, "Number": 49 }, { "CurrentHolder": null, "Number": 50 }, { "CurrentHolder": null, "Number": 51 }, { "CurrentHolder": null, "Number": 52 }, { "CurrentHolder": null, "Number": 53 }, { "CurrentHolder": null, "Number": 54 }, { "CurrentHolder": 11, "Number": 55 }, { "CurrentHolder": null, "Number": 56 }, { "CurrentHolder": null, "Number": 57 }, { "CurrentHolder": null, "Number": 58 }, { "CurrentHolder": null, "Number": 59 }, { "CurrentHolder": null, "Number": 60 }, { "CurrentHolder": null, "Number": 61 }, { "CurrentHolder": null, "Number": 62 }, { "CurrentHolder": 23, "Number": 63 }, { "CurrentHolder": null, "Number": 64 }, { "CurrentHolder": null, "Number": 65 }, { "CurrentHolder": null, "Number": 66 }, { "CurrentHolder": null, "Number": 67 }, { "CurrentHolder": null, "Number": 68 }, { "CurrentHolder": null, "Number": 69 }, { "CurrentHolder": null, "Number": 70 }, { "CurrentHolder": null, "Number": 71 }, { "CurrentHolder": null, "Number": 72 }, { "CurrentHolder": null, "Number": 73 }, { "CurrentHolder": null, "Number": 74 }, { "CurrentHolder": null, "Number": 75 }, { "CurrentHolder": null, "Number": 76 }, { "CurrentHolder": 8, "Number": 77 }, { "CurrentHolder": null, "Number": 78 }, { "CurrentHolder": null, "Number": 79 }, { "CurrentHolder": null, "Number": 80 }, { "CurrentHolder": 102, "Number": 81 }, { "CurrentHolder": null, "Number": 82 }, { "CurrentHolder": null, "Number": 83 }, { "CurrentHolder": null, "Number": 84 }, { "CurrentHolder": null, "Number": 85 }, { "CurrentHolder": null, "Number": 86 }, { "CurrentHolder": 142, "Number": 87 }, { "CurrentHolder": null, "Number": 88 }, { "CurrentHolder": null, "Number": 89 }, { "CurrentHolder": null, "Number": 90 }, { "CurrentHolder": null, "Number": 91 }, { "CurrentHolder": null, "Number": 92 }, { "CurrentHolder": null, "Number": 93 }, { "CurrentHolder": null, "Number": 94 }, { "CurrentHolder": null, "Number": 95 }, { "CurrentHolder": null, "Number": 96 }, { "CurrentHolder": null, "Number": 97 }, { "CurrentHolder": null, "Number": 98 }, { "CurrentHolder": null, "Number": 99 } ], "Staff_GameData": [ { "AchievementScore": 1180, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2089, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 42, "StaffID": 1, "StaffType": 0 }, { "AchievementScore": 100, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 135, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 2, "StaffType": 0 }, { "AchievementScore": 13, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -1, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 3, "StaffType": 0 }, { "AchievementScore": 332, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 539, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 4, "StaffType": 1 }, { "AchievementScore": 178, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 171, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 6, "StaffType": 2 }, { "AchievementScore": 246, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 112, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 7, "StaffType": 2 }, { "AchievementScore": 194, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 222, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 8, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 9, "StaffType": 0 }, { "AchievementScore": 550, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 978, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 10, "StaffType": 0 }, { "AchievementScore": 42, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -40, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 11, "StaffType": 0 }, { "AchievementScore": 29, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 12, "StaffType": 0 }, { "AchievementScore": 112, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 13, "StaffType": 0 }, { "AchievementScore": 12, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -53, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 14, "StaffType": 0 }, { "AchievementScore": 36, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -2, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 15, "StaffType": 0 }, { "AchievementScore": 92, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -15, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 3, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 17, "StaffType": 0 }, { "AchievementScore": 6, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -78, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 18, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -34, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 20, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -13, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 22, "StaffType": 0 }, { "AchievementScore": 50, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 55, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 23, "StaffType": 0 }, { "AchievementScore": 160, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 257, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 26, "StaffType": 3 }, { "AchievementScore": 624, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1165, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 28, "StaffType": 1 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -8, "ExpectedRankForTeam": 10, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 66, "StaffID": 30, "StaffType": 1 }, { "AchievementScore": 16, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -114, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 67, "StaffID": 33, "StaffType": 1 }, { "AchievementScore": 472, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 929, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 37, "StaffType": 3 }, { "AchievementScore": 1330, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2473, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 38, "StaffType": 3 }, { "AchievementScore": 190, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 96, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 69, "StaffID": 39, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 40, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -63, "ExpectedRankForTeam": 10, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 43, "StaffType": 3 }, { "AchievementScore": 18, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 44, "StaffType": 3 }, { "AchievementScore": 18, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -134, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 47, "StaffType": 2 }, { "AchievementScore": 414, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 530, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 48, "StaffType": 2 }, { "AchievementScore": 128, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 226, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 49, "StaffType": 2 }, { "AchievementScore": 576, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 823, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 50, "StaffType": 2 }, { "AchievementScore": 1060, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1895, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 51, "StaffType": 2 }, { "AchievementScore": 10, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -15, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 54, "StaffType": 2 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -111, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 55, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -80, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 56, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 58, "StaffType": 2 }, { "AchievementScore": 82, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -86, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 59, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 60, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -17, "ExpectedRankForTeam": 9, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 61, "StaffType": 2 }, { "AchievementScore": 28, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 63, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 34, "ExpectedRankForTeam": 3, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 64, "StaffType": 2 }, { "AchievementScore": 16, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 74, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 76, "StaffType": 0 }, { "AchievementScore": 452, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 589, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 45, "StaffID": 77, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 44, "StaffID": 78, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 80, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -9, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 81, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 41, "StaffID": 82, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -148, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 83, "StaffType": 0 }, { "AchievementScore": 25, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 79, "ExpectedRankForTeam": 9, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 87, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 41, "StaffID": 88, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 91, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 95, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 11, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 99, "StaffType": 0 }, { "AchievementScore": 26, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 64, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 102, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 105, "StaffType": 0 }, { "AchievementScore": 25, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 76, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 106, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 107, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 109, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 35, "StaffID": 110, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 114, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 115, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 116, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 117, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 119, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 120, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 121, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 123, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 127, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 128, "StaffType": 0 }, { "AchievementScore": 3, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 130, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 132, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 133, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 135, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 140, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 142, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 143, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 144, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 145, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 146, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 148, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 149, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 151, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 152, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 153, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 155, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 156, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 58, "StaffID": 158, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 60, "StaffID": 159, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 160, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 161, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 162, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 163, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 165, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 166, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 168, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 169, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 90, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 172, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 19, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 173, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 174, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 175, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 57, "StaffID": 178, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 179, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 61, "StaffID": 181, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 183, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 184, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 187, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 191, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 193, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 195, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 242, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 244, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 35, "StaffID": 245, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 247, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 248, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 252, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 253, "StaffType": 0 }, { "AchievementScore": 2, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -106, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 49, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 255, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 258, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 259, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 260, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 263, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 264, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 272, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 279, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 280, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 281, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 282, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 283, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 284, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 285, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 286, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 287, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 288, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 289, "StaffType": 0 }, { "AchievementScore": 1450, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2624, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 292, "StaffType": 4 }, { "AchievementScore": 22, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -242, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 293, "StaffType": 4 }, { "AchievementScore": 58, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -376, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 72, "StaffID": 299, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 300, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 301, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 302, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 303, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 304, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 305, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 43, "StaffID": 306, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 307, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 308, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 309, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 32, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 310, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 311, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 69, "StaffID": 312, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 72, "StaffID": 313, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 29, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 314, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 315, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 316, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 30, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 317, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 66, "StaffID": 318, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 71, "StaffID": 319, "StaffType": 4 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 320, "StaffType": 2 }, { "AchievementScore": 708, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 892, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 321, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 37, "StaffID": 322, "StaffType": 0 }, { "AchievementScore": 1784, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 3097, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 333, "StaffType": 1 }, { "AchievementScore": 26, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -247, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 334, "StaffType": 1 }, { "AchievementScore": 298, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 447, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 335, "StaffType": 1 }, { "AchievementScore": 1454, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2344, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 337, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 338, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 341, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 343, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 344, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 345, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 61, "StaffID": 361, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 364, "StaffType": 2 }, { "AchievementScore": 448, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 580, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 365, "StaffType": 1 }, { "AchievementScore": 40, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 33, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 366, "StaffType": 4 }, { "AchievementScore": 46, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -221, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 4, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 367, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 368, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 369, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 370, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 41, "StaffID": 373, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 374, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 375, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 37, "StaffID": 376, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 377, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 378, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 38, "StaffID": 379, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 380, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 381, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 382, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 383, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 384, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 385, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 386, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 387, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 38, "StaffID": 388, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 389, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 390, "StaffType": 0 }, { "AchievementScore": 710, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1040, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 391, "StaffType": 1 }, { "AchievementScore": 20, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 71, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 392, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 393, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 394, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 395, "StaffType": 2 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 397, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 4, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 398, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 36, "StaffID": 399, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 43, "StaffID": 400, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 44, "StaffID": 401, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 402, "StaffType": 0 }, { "AchievementScore": 696, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 985, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 403, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -135, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 404, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 405, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 36, "StaffID": 406, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 407, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 90, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 45, "StaffID": 408, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 409, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 410, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 41, "StaffID": 411, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 413, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 32, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 414, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 33, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 415, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 416, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 417, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 43, "StaffID": 418, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 419, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 420, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 421, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 422, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 423, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 424, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 425, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 426, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 427, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 428, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 429, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 430, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 58, "StaffID": 431, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 432, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 433, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 59, "StaffID": 434, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 435, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 436, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 437, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 39, "StaffID": 438, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 439, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 68, "StaffID": 536, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 64, "StaffID": 537, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 538, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 66, "StaffID": 539, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 540, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 541, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 66, "StaffID": 542, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 543, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 544, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 545, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 546, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 547, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 548, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 549, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 550, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 551, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 554, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 555, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 556, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 557, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 567, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 568, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 600, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 601, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 602, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 603, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 604, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 605, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 606, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 607, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 35, "StaffID": 608, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 609, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 610, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 611, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 612, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 613, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 614, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 615, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 616, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 617, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 618, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 619, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 620, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 621, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 622, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 623, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 624, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 625, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 626, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 628, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 630, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 633, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 635, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 636, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 637, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 638, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 639, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 640, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 641, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 642, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 643, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 644, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 645, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 646, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 647, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 648, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 649, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 650, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 651, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 652, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 653, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 654, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 655, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 656, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 657, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 658, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 659, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 660, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 661, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 662, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 663, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 664, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 665, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 666, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 667, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 668, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 669, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 670, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 671, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 672, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 673, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 674, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 677, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 678, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 679, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 680, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 681, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 682, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 683, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 684, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 685, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 686, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 687, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 688, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 689, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 690, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 691, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 692, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 693, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 694, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 695, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 696, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 697, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 62, "StaffID": 698, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 699, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 62, "StaffID": 700, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 701, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 702, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 703, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 704, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 705, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 59, "StaffID": 706, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 707, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 708, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 64, "StaffID": 709, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 710, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 711, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 712, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 713, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 714, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 715, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 716, "StaffType": 0 }, { "AchievementScore": 1180, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2089, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 42, "StaffID": 1, "StaffType": 0 }, { "AchievementScore": 100, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 135, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 2, "StaffType": 0 }, { "AchievementScore": 13, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -1, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 3, "StaffType": 0 }, { "AchievementScore": 332, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 539, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 4, "StaffType": 1 }, { "AchievementScore": 178, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 171, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 6, "StaffType": 2 }, { "AchievementScore": 246, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 112, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 7, "StaffType": 2 }, { "AchievementScore": 194, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 222, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 8, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 9, "StaffType": 0 }, { "AchievementScore": 550, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 978, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 10, "StaffType": 0 }, { "AchievementScore": 42, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -40, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 11, "StaffType": 0 }, { "AchievementScore": 29, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 12, "StaffType": 0 }, { "AchievementScore": 112, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 13, "StaffType": 0 }, { "AchievementScore": 12, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -53, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 14, "StaffType": 0 }, { "AchievementScore": 36, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -2, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 1, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 15, "StaffType": 0 }, { "AchievementScore": 92, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -15, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 3, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 17, "StaffType": 0 }, { "AchievementScore": 6, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -78, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 18, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -34, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 20, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -13, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 22, "StaffType": 0 }, { "AchievementScore": 50, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 55, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 23, "StaffType": 0 }, { "AchievementScore": 160, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 257, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 26, "StaffType": 3 }, { "AchievementScore": 624, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1165, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 28, "StaffType": 1 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -8, "ExpectedRankForTeam": 10, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 66, "StaffID": 30, "StaffType": 1 }, { "AchievementScore": 16, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -114, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 67, "StaffID": 33, "StaffType": 1 }, { "AchievementScore": 472, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 929, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 37, "StaffType": 3 }, { "AchievementScore": 1330, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2473, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 38, "StaffType": 3 }, { "AchievementScore": 190, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 96, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 69, "StaffID": 39, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 40, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -63, "ExpectedRankForTeam": 10, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 43, "StaffType": 3 }, { "AchievementScore": 18, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 44, "StaffType": 3 }, { "AchievementScore": 18, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -134, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 47, "StaffType": 2 }, { "AchievementScore": 414, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 530, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 48, "StaffType": 2 }, { "AchievementScore": 128, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 226, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 49, "StaffType": 2 }, { "AchievementScore": 576, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 823, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 50, "StaffType": 2 }, { "AchievementScore": 1060, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1895, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 51, "StaffType": 2 }, { "AchievementScore": 10, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -15, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 54, "StaffType": 2 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -111, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 55, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -80, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 56, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 58, "StaffType": 2 }, { "AchievementScore": 82, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -86, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 59, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 60, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -17, "ExpectedRankForTeam": 9, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 61, "StaffType": 2 }, { "AchievementScore": 28, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 4, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 63, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 34, "ExpectedRankForTeam": 3, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 55, "StaffID": 64, "StaffType": 2 }, { "AchievementScore": 16, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 74, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 76, "StaffType": 0 }, { "AchievementScore": 452, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 589, "ExpectedRankForTeam": 2, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 45, "StaffID": 77, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 44, "StaffID": 78, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 80, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -9, "ExpectedRankForTeam": 6, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 81, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 41, "StaffID": 82, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -148, "ExpectedRankForTeam": 8, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 83, "StaffType": 0 }, { "AchievementScore": 25, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 79, "ExpectedRankForTeam": 9, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 87, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 41, "StaffID": 88, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 91, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 1, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 95, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 11, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 99, "StaffType": 0 }, { "AchievementScore": 26, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 64, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 102, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 105, "StaffType": 0 }, { "AchievementScore": 25, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 76, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 106, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 107, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 109, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 35, "StaffID": 110, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 114, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 115, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 116, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 117, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 119, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 120, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 121, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 123, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 127, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 128, "StaffType": 0 }, { "AchievementScore": 3, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 130, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 132, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 133, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 135, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 140, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 142, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 143, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 144, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 145, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 146, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 148, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 149, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 18, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 151, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 152, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 153, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 155, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 156, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 58, "StaffID": 158, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 60, "StaffID": 159, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 160, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 161, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 162, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 163, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 165, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 166, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 168, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 169, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 90, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 172, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 19, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 173, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 174, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 175, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 57, "StaffID": 178, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 179, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 61, "StaffID": 181, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 56, "StaffID": 183, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 184, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 187, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 191, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 193, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 195, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 242, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 244, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 35, "StaffID": 245, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 247, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 248, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 252, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 253, "StaffType": 0 }, { "AchievementScore": 2, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -106, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 49, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 255, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 258, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 259, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 260, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 263, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 3, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 264, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 272, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 279, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 280, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 36, "StaffID": 281, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 282, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 283, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 284, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 285, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 286, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 287, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 288, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 289, "StaffType": 0 }, { "AchievementScore": 1450, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2624, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 292, "StaffType": 4 }, { "AchievementScore": 22, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -242, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 293, "StaffType": 4 }, { "AchievementScore": 58, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -376, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 72, "StaffID": 299, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 300, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 301, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 302, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 303, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 304, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 305, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 43, "StaffID": 306, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 307, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 308, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 28, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 309, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 32, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 310, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 311, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 69, "StaffID": 312, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 72, "StaffID": 313, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 29, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 314, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 68, "StaffID": 315, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 31, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 316, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 30, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 317, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 66, "StaffID": 318, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 71, "StaffID": 319, "StaffType": 4 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 320, "StaffType": 2 }, { "AchievementScore": 708, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 892, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 321, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 80, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 37, "StaffID": 322, "StaffType": 0 }, { "AchievementScore": 1784, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 3097, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 333, "StaffType": 1 }, { "AchievementScore": 26, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -247, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 67, "StaffID": 334, "StaffType": 1 }, { "AchievementScore": 298, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 447, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 335, "StaffType": 1 }, { "AchievementScore": 1454, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 2344, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 337, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 338, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 14, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 65, "StaffID": 341, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 343, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 344, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 345, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 61, "StaffID": 361, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 364, "StaffType": 2 }, { "AchievementScore": 448, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 580, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 365, "StaffType": 1 }, { "AchievementScore": 40, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 33, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 366, "StaffType": 4 }, { "AchievementScore": 46, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -221, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 4, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 367, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 65, "StaffID": 368, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 369, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 370, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 41, "StaffID": 373, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 374, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 375, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 23, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 37, "StaffID": 376, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 377, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 378, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 38, "StaffID": 379, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 380, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 381, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 11, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 37, "StaffID": 382, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 383, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 384, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 385, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 386, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 39, "StaffID": 387, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 38, "StaffID": 388, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 40, "StaffID": 389, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 40, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 41, "StaffID": 390, "StaffType": 0 }, { "AchievementScore": 710, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 1040, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 64, "StaffID": 391, "StaffType": 1 }, { "AchievementScore": 20, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 71, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 392, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 24, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 393, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 394, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 395, "StaffType": 2 }, { "AchievementScore": 4, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 59, "StaffID": 397, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 4, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 27, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 38, "StaffID": 398, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 36, "StaffID": 399, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 8, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 43, "StaffID": 400, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 44, "StaffID": 401, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 402, "StaffType": 0 }, { "AchievementScore": 696, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 985, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 403, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": -135, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 404, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 43, "StaffID": 405, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 36, "StaffID": 406, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 407, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 90, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 45, "StaffID": 408, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 41, "StaffID": 409, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 410, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.10000000149011612, "Retired": 0, "RetirementAge": 41, "StaffID": 411, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 413, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 32, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 414, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 33, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 415, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 416, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 39, "StaffID": 417, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 10, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 43, "StaffID": 418, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 42, "StaffID": 419, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 420, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 421, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 422, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 13, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 423, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 424, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 425, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 57, "StaffID": 426, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 59, "StaffID": 427, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 2, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 60, "StaffID": 428, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 429, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 58, "StaffID": 430, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 4, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 58, "StaffID": 431, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 3, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 62, "StaffID": 432, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 433, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 59, "StaffID": 434, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 435, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 70, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 436, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 40, "StaffID": 437, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 1, "RetirementAge": 39, "StaffID": 438, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 20, "PermaTraitSpawnBoost": 0.05000000074505806, "Retired": 0, "RetirementAge": 38, "StaffID": 439, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 68, "StaffID": 536, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 20, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 64, "StaffID": 537, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 538, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 21, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 66, "StaffID": 539, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 17, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 540, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 22, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 541, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 66, "StaffID": 542, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 12, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 543, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 25, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 544, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 15, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 545, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 26, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 546, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 10, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 60, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 547, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 5, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 40, "StaffID": 548, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 6, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 549, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 50, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 550, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 9, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 551, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 7, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 30, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 554, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 1, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 555, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 556, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 16, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 557, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 567, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 568, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 600, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 601, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 602, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 603, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 604, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 605, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 606, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 607, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 35, "StaffID": 608, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 609, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 610, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 611, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 612, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 613, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 614, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 615, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 616, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 617, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 618, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 619, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 620, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 35, "StaffID": 621, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 622, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 623, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 624, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 625, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 626, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 628, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 630, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 633, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 635, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 636, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 637, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 638, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 639, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 640, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 641, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 642, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 63, "StaffID": 643, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 61, "StaffID": 644, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 645, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 646, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 2, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 647, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 648, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 649, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 650, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 651, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 652, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 653, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 654, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 655, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 656, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 657, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 658, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 659, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 660, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 661, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 662, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 663, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": 3, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 664, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 665, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 666, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 667, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 668, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 669, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 670, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 671, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 672, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 673, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 40, "StaffID": 674, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 677, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 678, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 679, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 680, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 681, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 682, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 1, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 683, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": 2, "BestTeamFormula": 1, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 0, "RetirementAge": 70, "StaffID": 684, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 685, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 686, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 687, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 688, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 689, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 690, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 691, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 692, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 693, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 694, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 57, "StaffID": 695, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 696, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 697, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 62, "StaffID": 698, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 699, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 62, "StaffID": 700, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 61, "StaffID": 701, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 65, "StaffID": 702, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 703, "StaffType": 3 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 704, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 67, "StaffID": 705, "StaffType": 4 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 59, "StaffID": 706, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 70, "StaffID": 707, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 63, "StaffID": 708, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 64, "StaffID": 709, "StaffType": 1 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 60, "StaffID": 710, "StaffType": 2 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 711, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 39, "StaffID": 712, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 36, "StaffID": 713, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 714, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 38, "StaffID": 715, "StaffType": 0 }, { "AchievementScore": 0, "BestF1PosInTeamSinceGameStart": null, "BestTeamFormula": null, "DevelopmentPlan": null, "ExpectedQualityScore": 0, "ExpectedRankForTeam": 5, "ExpectedTimeScore": 0, "PermaTraitSpawnBoost": 0, "Retired": 1, "RetirementAge": 37, "StaffID": 716, "StaffType": 0 } ], "Staff_PerformanceStats": [ { "Max": 100, "StaffID": 4, "StatID": 0, "Val": 87 }, { "Max": 100, "StaffID": 4, "StatID": 1, "Val": 89 }, { "Max": 100, "StaffID": 4, "StatID": 14, "Val": 88 }, { "Max": 100, "StaffID": 4, "StatID": 15, "Val": 87 }, { "Max": 100, "StaffID": 4, "StatID": 16, "Val": 91 }, { "Max": 100, "StaffID": 4, "StatID": 17, "Val": 88 }, { "Max": 100, "StaffID": 6, "StatID": 13, "Val": 85 }, { "Max": 100, "StaffID": 6, "StatID": 25, "Val": 85 }, { "Max": 100, "StaffID": 7, "StatID": 13, "Val": 88 }, { "Max": 100, "StaffID": 7, "StatID": 25, "Val": 89 }, { "Max": 100, "StaffID": 13, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 13, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 26, "StatID": 19, "Val": 92 }, { "Max": 100, "StaffID": 26, "StatID": 20, "Val": 90 }, { "Max": 100, "StaffID": 26, "StatID": 26, "Val": 90 }, { "Max": 100, "StaffID": 26, "StatID": 27, "Val": 89 }, { "Max": 100, "StaffID": 26, "StatID": 28, "Val": 91 }, { "Max": 100, "StaffID": 26, "StatID": 29, "Val": 89 }, { "Max": 100, "StaffID": 26, "StatID": 30, "Val": 88 }, { "Max": 100, "StaffID": 26, "StatID": 31, "Val": 89 }, { "Max": 100, "StaffID": 28, "StatID": 0, "Val": 92 }, { "Max": 100, "StaffID": 28, "StatID": 1, "Val": 95 }, { "Max": 100, "StaffID": 28, "StatID": 14, "Val": 94 }, { "Max": 100, "StaffID": 28, "StatID": 15, "Val": 92 }, { "Max": 100, "StaffID": 28, "StatID": 16, "Val": 95 }, { "Max": 100, "StaffID": 28, "StatID": 17, "Val": 90 }, { "Max": 100, "StaffID": 30, "StatID": 0, "Val": 87 }, { "Max": 100, "StaffID": 30, "StatID": 1, "Val": 84 }, { "Max": 100, "StaffID": 30, "StatID": 14, "Val": 84 }, { "Max": 100, "StaffID": 30, "StatID": 15, "Val": 86 }, { "Max": 100, "StaffID": 30, "StatID": 16, "Val": 85 }, { "Max": 100, "StaffID": 30, "StatID": 17, "Val": 88 }, { "Max": 100, "StaffID": 33, "StatID": 1, "Val": 86 }, { "Max": 100, "StaffID": 33, "StatID": 14, "Val": 85 }, { "Max": 100, "StaffID": 33, "StatID": 15, "Val": 83 }, { "Max": 100, "StaffID": 33, "StatID": 16, "Val": 86 }, { "Max": 100, "StaffID": 33, "StatID": 17, "Val": 83 }, { "Max": 100, "StaffID": 37, "StatID": 19, "Val": 88 }, { "Max": 100, "StaffID": 37, "StatID": 20, "Val": 95 }, { "Max": 100, "StaffID": 37, "StatID": 26, "Val": 95 }, { "Max": 100, "StaffID": 37, "StatID": 27, "Val": 94 }, { "Max": 100, "StaffID": 37, "StatID": 28, "Val": 92 }, { "Max": 100, "StaffID": 37, "StatID": 29, "Val": 95 }, { "Max": 100, "StaffID": 37, "StatID": 30, "Val": 93 }, { "Max": 100, "StaffID": 37, "StatID": 31, "Val": 90 }, { "Max": 100, "StaffID": 38, "StatID": 19, "Val": 89 }, { "Max": 100, "StaffID": 38, "StatID": 20, "Val": 88 }, { "Max": 100, "StaffID": 38, "StatID": 26, "Val": 93 }, { "Max": 100, "StaffID": 38, "StatID": 27, "Val": 92 }, { "Max": 100, "StaffID": 38, "StatID": 28, "Val": 90 }, { "Max": 100, "StaffID": 38, "StatID": 29, "Val": 90 }, { "Max": 100, "StaffID": 38, "StatID": 30, "Val": 94 }, { "Max": 100, "StaffID": 38, "StatID": 31, "Val": 93 }, { "Max": 100, "StaffID": 39, "StatID": 19, "Val": 82 }, { "Max": 100, "StaffID": 39, "StatID": 20, "Val": 81 }, { "Max": 100, "StaffID": 39, "StatID": 26, "Val": 82 }, { "Max": 100, "StaffID": 39, "StatID": 27, "Val": 83 }, { "Max": 100, "StaffID": 39, "StatID": 28, "Val": 82 }, { "Max": 100, "StaffID": 39, "StatID": 29, "Val": 80 }, { "Max": 100, "StaffID": 39, "StatID": 30, "Val": 83 }, { "Max": 100, "StaffID": 39, "StatID": 31, "Val": 82 }, { "Max": 100, "StaffID": 40, "StatID": 20, "Val": 86 }, { "Max": 100, "StaffID": 40, "StatID": 26, "Val": 87 }, { "Max": 100, "StaffID": 40, "StatID": 27, "Val": 86 }, { "Max": 100, "StaffID": 40, "StatID": 28, "Val": 85 }, { "Max": 100, "StaffID": 40, "StatID": 29, "Val": 86 }, { "Max": 100, "StaffID": 40, "StatID": 30, "Val": 85 }, { "Max": 100, "StaffID": 40, "StatID": 31, "Val": 85 }, { "Max": 100, "StaffID": 43, "StatID": 19, "Val": 85 }, { "Max": 100, "StaffID": 43, "StatID": 20, "Val": 84 }, { "Max": 100, "StaffID": 43, "StatID": 27, "Val": 86 }, { "Max": 100, "StaffID": 43, "StatID": 28, "Val": 85 }, { "Max": 100, "StaffID": 43, "StatID": 29, "Val": 83 }, { "Max": 100, "StaffID": 43, "StatID": 30, "Val": 86 }, { "Max": 100, "StaffID": 43, "StatID": 31, "Val": 85 }, { "Max": 100, "StaffID": 44, "StatID": 19, "Val": 86 }, { "Max": 100, "StaffID": 44, "StatID": 20, "Val": 85 }, { "Max": 100, "StaffID": 44, "StatID": 26, "Val": 86 }, { "Max": 100, "StaffID": 44, "StatID": 27, "Val": 87 }, { "Max": 100, "StaffID": 44, "StatID": 28, "Val": 86 }, { "Max": 100, "StaffID": 44, "StatID": 29, "Val": 84 }, { "Max": 100, "StaffID": 44, "StatID": 30, "Val": 87 }, { "Max": 100, "StaffID": 44, "StatID": 31, "Val": 86 }, { "Max": 100, "StaffID": 47, "StatID": 13, "Val": 84 }, { "Max": 100, "StaffID": 47, "StatID": 25, "Val": 86 }, { "Max": 100, "StaffID": 48, "StatID": 13, "Val": 87 }, { "Max": 100, "StaffID": 48, "StatID": 25, "Val": 89 }, { "Max": 100, "StaffID": 49, "StatID": 13, "Val": 86 }, { "Max": 100, "StaffID": 49, "StatID": 25, "Val": 87 }, { "Max": 100, "StaffID": 50, "StatID": 13, "Val": 96 }, { "Max": 100, "StaffID": 50, "StatID": 25, "Val": 92 }, { "Max": 100, "StaffID": 51, "StatID": 13, "Val": 97 }, { "Max": 100, "StaffID": 51, "StatID": 25, "Val": 91 }, { "Max": 100, "StaffID": 54, "StatID": 13, "Val": 77 }, { "Max": 100, "StaffID": 54, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 55, "StatID": 13, "Val": 81 }, { "Max": 100, "StaffID": 55, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 56, "StatID": 13, "Val": 82 }, { "Max": 100, "StaffID": 56, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 58, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 59, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 59, "StatID": 25, "Val": 87 }, { "Max": 100, "StaffID": 60, "StatID": 13, "Val": 83 }, { "Max": 100, "StaffID": 60, "StatID": 25, "Val": 85 }, { "Max": 100, "StaffID": 61, "StatID": 13, "Val": 84 }, { "Max": 100, "StaffID": 61, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 63, "StatID": 13, "Val": 82 }, { "Max": 100, "StaffID": 63, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 64, "StatID": 13, "Val": 82 }, { "Max": 100, "StaffID": 64, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 109, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 109, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 110, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 116, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 119, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 146, "StatID": 25, "Val": 77 }, { "Max": 100, "StaffID": 148, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 148, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 149, "StatID": 13, "Val": 78 }, { "Max": 100, "StaffID": 149, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 151, "StatID": 13, "Val": 78 }, { "Max": 100, "StaffID": 151, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 152, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 152, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 153, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 155, "StatID": 13, "Val": 78 }, { "Max": 100, "StaffID": 155, "StatID": 25, "Val": 77 }, { "Max": 100, "StaffID": 158, "StatID": 13, "Val": 77 }, { "Max": 100, "StaffID": 158, "StatID": 25, "Val": 73 }, { "Max": 100, "StaffID": 159, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 160, "StatID": 13, "Val": 77 }, { "Max": 100, "StaffID": 160, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 161, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 161, "StatID": 25, "Val": 80 }, { "Max": 100, "StaffID": 162, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 162, "StatID": 25, "Val": 73 }, { "Max": 100, "StaffID": 163, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 163, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 165, "StatID": 13, "Val": 70 }, { "Max": 100, "StaffID": 165, "StatID": 25, "Val": 73 }, { "Max": 100, "StaffID": 166, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 166, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 168, "StatID": 13, "Val": 82 }, { "Max": 100, "StaffID": 168, "StatID": 25, "Val": 81 }, { "Max": 100, "StaffID": 169, "StatID": 13, "Val": 79 }, { "Max": 100, "StaffID": 169, "StatID": 25, "Val": 80 }, { "Max": 100, "StaffID": 172, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 172, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 173, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 173, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 174, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 174, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 175, "StatID": 13, "Val": 77 }, { "Max": 100, "StaffID": 175, "StatID": 25, "Val": 80 }, { "Max": 100, "StaffID": 178, "StatID": 25, "Val": 77 }, { "Max": 100, "StaffID": 179, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 179, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 181, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 183, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 183, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 184, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 184, "StatID": 25, "Val": 73 }, { "Max": 100, "StaffID": 187, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 187, "StatID": 25, "Val": 71 }, { "Max": 100, "StaffID": 191, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 191, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 193, "StatID": 25, "Val": 71 }, { "Max": 100, "StaffID": 195, "StatID": 13, "Val": 71 }, { "Max": 100, "StaffID": 195, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 247, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 247, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 287, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 292, "StatID": 11, "Val": 95 }, { "Max": 100, "StaffID": 292, "StatID": 22, "Val": 94 }, { "Max": 100, "StaffID": 292, "StatID": 23, "Val": 95 }, { "Max": 100, "StaffID": 292, "StatID": 24, "Val": 93 }, { "Max": 100, "StaffID": 293, "StatID": 11, "Val": 87 }, { "Max": 100, "StaffID": 293, "StatID": 22, "Val": 90 }, { "Max": 100, "StaffID": 293, "StatID": 23, "Val": 88 }, { "Max": 100, "StaffID": 293, "StatID": 24, "Val": 90 }, { "Max": 100, "StaffID": 299, "StatID": 11, "Val": 90 }, { "Max": 100, "StaffID": 299, "StatID": 22, "Val": 86 }, { "Max": 100, "StaffID": 299, "StatID": 23, "Val": 90 }, { "Max": 100, "StaffID": 299, "StatID": 24, "Val": 95 }, { "Max": 100, "StaffID": 306, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 306, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 308, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 309, "StatID": 11, "Val": 78 }, { "Max": 100, "StaffID": 309, "StatID": 22, "Val": 80 }, { "Max": 100, "StaffID": 309, "StatID": 23, "Val": 76 }, { "Max": 100, "StaffID": 309, "StatID": 24, "Val": 78 }, { "Max": 100, "StaffID": 310, "StatID": 11, "Val": 78 }, { "Max": 100, "StaffID": 310, "StatID": 22, "Val": 82 }, { "Max": 100, "StaffID": 310, "StatID": 23, "Val": 77 }, { "Max": 100, "StaffID": 310, "StatID": 24, "Val": 80 }, { "Max": 100, "StaffID": 311, "StatID": 11, "Val": 75 }, { "Max": 100, "StaffID": 311, "StatID": 22, "Val": 76 }, { "Max": 100, "StaffID": 311, "StatID": 23, "Val": 73 }, { "Max": 100, "StaffID": 311, "StatID": 24, "Val": 75 }, { "Max": 100, "StaffID": 312, "StatID": 11, "Val": 76 }, { "Max": 100, "StaffID": 312, "StatID": 22, "Val": 76 }, { "Max": 100, "StaffID": 312, "StatID": 24, "Val": 75 }, { "Max": 100, "StaffID": 313, "StatID": 11, "Val": 74 }, { "Max": 100, "StaffID": 313, "StatID": 22, "Val": 74 }, { "Max": 100, "StaffID": 313, "StatID": 23, "Val": 72 }, { "Max": 100, "StaffID": 314, "StatID": 11, "Val": 80 }, { "Max": 100, "StaffID": 314, "StatID": 22, "Val": 82 }, { "Max": 100, "StaffID": 314, "StatID": 23, "Val": 79 }, { "Max": 100, "StaffID": 314, "StatID": 24, "Val": 81 }, { "Max": 100, "StaffID": 315, "StatID": 11, "Val": 80 }, { "Max": 100, "StaffID": 315, "StatID": 22, "Val": 82 }, { "Max": 100, "StaffID": 315, "StatID": 23, "Val": 79 }, { "Max": 100, "StaffID": 315, "StatID": 24, "Val": 81 }, { "Max": 100, "StaffID": 316, "StatID": 11, "Val": 79 }, { "Max": 100, "StaffID": 316, "StatID": 22, "Val": 81 }, { "Max": 100, "StaffID": 316, "StatID": 23, "Val": 80 }, { "Max": 100, "StaffID": 316, "StatID": 24, "Val": 82 }, { "Max": 100, "StaffID": 317, "StatID": 11, "Val": 86 }, { "Max": 100, "StaffID": 317, "StatID": 22, "Val": 88 }, { "Max": 100, "StaffID": 317, "StatID": 23, "Val": 81 }, { "Max": 100, "StaffID": 317, "StatID": 24, "Val": 83 }, { "Max": 100, "StaffID": 318, "StatID": 11, "Val": 78 }, { "Max": 100, "StaffID": 318, "StatID": 22, "Val": 80 }, { "Max": 100, "StaffID": 318, "StatID": 23, "Val": 77 }, { "Max": 100, "StaffID": 318, "StatID": 24, "Val": 79 }, { "Max": 100, "StaffID": 319, "StatID": 11, "Val": 78 }, { "Max": 100, "StaffID": 319, "StatID": 22, "Val": 80 }, { "Max": 100, "StaffID": 319, "StatID": 23, "Val": 77 }, { "Max": 100, "StaffID": 319, "StatID": 24, "Val": 79 }, { "Max": 100, "StaffID": 320, "StatID": 13, "Val": 90 }, { "Max": 100, "StaffID": 320, "StatID": 25, "Val": 87 }, { "Max": 100, "StaffID": 321, "StatID": 13, "Val": 85 }, { "Max": 100, "StaffID": 321, "StatID": 25, "Val": 86 }, { "Max": 100, "StaffID": 6, "StatID": 43, "Val": 86 }, { "Max": 100, "StaffID": 7, "StatID": 43, "Val": 90 }, { "Max": 100, "StaffID": 47, "StatID": 43, "Val": 85 }, { "Max": 100, "StaffID": 48, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 49, "StatID": 43, "Val": 86 }, { "Max": 100, "StaffID": 50, "StatID": 43, "Val": 91 }, { "Max": 100, "StaffID": 51, "StatID": 43, "Val": 92 }, { "Max": 100, "StaffID": 54, "StatID": 43, "Val": 87 }, { "Max": 100, "StaffID": 55, "StatID": 43, "Val": 82 }, { "Max": 100, "StaffID": 56, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 58, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 59, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 60, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 61, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 63, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 64, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 146, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 148, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 149, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 151, "StatID": 43, "Val": 79 }, { "Max": 100, "StaffID": 152, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 153, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 155, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 159, "StatID": 43, "Val": 80 }, { "Max": 100, "StaffID": 160, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 161, "StatID": 43, "Val": 79 }, { "Max": 100, "StaffID": 162, "StatID": 43, "Val": 73 }, { "Max": 100, "StaffID": 163, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 165, "StatID": 43, "Val": 71 }, { "Max": 100, "StaffID": 166, "StatID": 43, "Val": 73 }, { "Max": 100, "StaffID": 168, "StatID": 43, "Val": 81 }, { "Max": 100, "StaffID": 169, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 172, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 174, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 175, "StatID": 43, "Val": 79 }, { "Max": 100, "StaffID": 178, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 179, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 181, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 183, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 184, "StatID": 43, "Val": 74 }, { "Max": 100, "StaffID": 187, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 191, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 193, "StatID": 43, "Val": 75 }, { "Max": 100, "StaffID": 195, "StatID": 43, "Val": 72 }, { "Max": 100, "StaffID": 320, "StatID": 43, "Val": 95 }, { "Max": 100, "StaffID": 321, "StatID": 43, "Val": 85 }, { "Max": 100, "StaffID": 333, "StatID": 0, "Val": 95 }, { "Max": 100, "StaffID": 333, "StatID": 1, "Val": 93 }, { "Max": 100, "StaffID": 333, "StatID": 14, "Val": 92 }, { "Max": 100, "StaffID": 333, "StatID": 15, "Val": 95 }, { "Max": 100, "StaffID": 333, "StatID": 16, "Val": 94 }, { "Max": 100, "StaffID": 333, "StatID": 17, "Val": 93 }, { "Max": 100, "StaffID": 334, "StatID": 0, "Val": 89 }, { "Max": 100, "StaffID": 334, "StatID": 1, "Val": 88 }, { "Max": 100, "StaffID": 334, "StatID": 14, "Val": 87 }, { "Max": 100, "StaffID": 334, "StatID": 15, "Val": 89 }, { "Max": 100, "StaffID": 334, "StatID": 16, "Val": 90 }, { "Max": 100, "StaffID": 334, "StatID": 17, "Val": 90 }, { "Max": 100, "StaffID": 335, "StatID": 0, "Val": 86 }, { "Max": 100, "StaffID": 335, "StatID": 1, "Val": 88 }, { "Max": 100, "StaffID": 335, "StatID": 14, "Val": 87 }, { "Max": 100, "StaffID": 335, "StatID": 15, "Val": 85 }, { "Max": 100, "StaffID": 335, "StatID": 16, "Val": 89 }, { "Max": 100, "StaffID": 335, "StatID": 17, "Val": 83 }, { "Max": 100, "StaffID": 337, "StatID": 19, "Val": 87 }, { "Max": 100, "StaffID": 337, "StatID": 20, "Val": 89 }, { "Max": 100, "StaffID": 337, "StatID": 26, "Val": 88 }, { "Max": 100, "StaffID": 337, "StatID": 27, "Val": 87 }, { "Max": 100, "StaffID": 337, "StatID": 28, "Val": 86 }, { "Max": 100, "StaffID": 337, "StatID": 29, "Val": 87 }, { "Max": 100, "StaffID": 337, "StatID": 30, "Val": 88 }, { "Max": 100, "StaffID": 337, "StatID": 31, "Val": 87 }, { "Max": 100, "StaffID": 341, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 341, "StatID": 43, "Val": 75 }, { "Max": 100, "StaffID": 343, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 343, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 343, "StatID": 43, "Val": 75 }, { "Max": 100, "StaffID": 344, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 344, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 344, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 345, "StatID": 13, "Val": 72 }, { "Max": 100, "StaffID": 345, "StatID": 25, "Val": 70 }, { "Max": 100, "StaffID": 345, "StatID": 43, "Val": 74 }, { "Max": 100, "StaffID": 361, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 361, "StatID": 43, "Val": 72 }, { "Max": 100, "StaffID": 364, "StatID": 13, "Val": 72 }, { "Max": 100, "StaffID": 364, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 364, "StatID": 43, "Val": 70 }, { "Max": 100, "StaffID": 365, "StatID": 0, "Val": 89 }, { "Max": 100, "StaffID": 365, "StatID": 1, "Val": 87 }, { "Max": 100, "StaffID": 365, "StatID": 14, "Val": 85 }, { "Max": 100, "StaffID": 365, "StatID": 15, "Val": 86 }, { "Max": 100, "StaffID": 365, "StatID": 16, "Val": 88 }, { "Max": 100, "StaffID": 365, "StatID": 17, "Val": 86 }, { "Max": 100, "StaffID": 366, "StatID": 11, "Val": 86 }, { "Max": 100, "StaffID": 366, "StatID": 22, "Val": 92 }, { "Max": 100, "StaffID": 366, "StatID": 23, "Val": 88 }, { "Max": 100, "StaffID": 366, "StatID": 24, "Val": 90 }, { "Max": 100, "StaffID": 367, "StatID": 11, "Val": 92 }, { "Max": 100, "StaffID": 367, "StatID": 22, "Val": 90 }, { "Max": 100, "StaffID": 367, "StatID": 23, "Val": 94 }, { "Max": 100, "StaffID": 367, "StatID": 24, "Val": 92 }, { "Max": 100, "StaffID": 368, "StatID": 11, "Val": 80 }, { "Max": 100, "StaffID": 368, "StatID": 22, "Val": 78 }, { "Max": 100, "StaffID": 368, "StatID": 23, "Val": 80 }, { "Max": 100, "StaffID": 368, "StatID": 24, "Val": 84 }, { "Max": 100, "StaffID": 369, "StatID": 19, "Val": 86 }, { "Max": 100, "StaffID": 369, "StatID": 20, "Val": 85 }, { "Max": 100, "StaffID": 369, "StatID": 26, "Val": 84 }, { "Max": 100, "StaffID": 369, "StatID": 27, "Val": 83 }, { "Max": 100, "StaffID": 369, "StatID": 28, "Val": 82 }, { "Max": 100, "StaffID": 369, "StatID": 29, "Val": 83 }, { "Max": 100, "StaffID": 369, "StatID": 30, "Val": 84 }, { "Max": 100, "StaffID": 369, "StatID": 31, "Val": 85 }, { "Max": 100, "StaffID": 370, "StatID": 11, "Val": 84 }, { "Max": 100, "StaffID": 370, "StatID": 22, "Val": 86 }, { "Max": 100, "StaffID": 370, "StatID": 23, "Val": 88 }, { "Max": 100, "StaffID": 370, "StatID": 24, "Val": 88 }, { "Max": 100, "StaffID": 374, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 374, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 386, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 390, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 391, "StatID": 0, "Val": 88 }, { "Max": 100, "StaffID": 391, "StatID": 1, "Val": 85 }, { "Max": 100, "StaffID": 391, "StatID": 14, "Val": 84 }, { "Max": 100, "StaffID": 391, "StatID": 15, "Val": 80 }, { "Max": 100, "StaffID": 391, "StatID": 16, "Val": 85 }, { "Max": 100, "StaffID": 391, "StatID": 17, "Val": 89 }, { "Max": 100, "StaffID": 392, "StatID": 11, "Val": 91 }, { "Max": 100, "StaffID": 392, "StatID": 22, "Val": 90 }, { "Max": 100, "StaffID": 392, "StatID": 23, "Val": 92 }, { "Max": 100, "StaffID": 392, "StatID": 24, "Val": 91 }, { "Max": 100, "StaffID": 393, "StatID": 13, "Val": 85 }, { "Max": 100, "StaffID": 393, "StatID": 25, "Val": 84 }, { "Max": 100, "StaffID": 393, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 397, "StatID": 11, "Val": 84 }, { "Max": 100, "StaffID": 397, "StatID": 22, "Val": 84 }, { "Max": 100, "StaffID": 397, "StatID": 23, "Val": 84 }, { "Max": 100, "StaffID": 397, "StatID": 24, "Val": 83 }, { "Max": 100, "StaffID": 400, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 400, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 401, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 403, "StatID": 0, "Val": 83 }, { "Max": 100, "StaffID": 403, "StatID": 1, "Val": 85 }, { "Max": 100, "StaffID": 403, "StatID": 14, "Val": 84 }, { "Max": 100, "StaffID": 403, "StatID": 15, "Val": 81 }, { "Max": 100, "StaffID": 403, "StatID": 16, "Val": 83 }, { "Max": 100, "StaffID": 403, "StatID": 17, "Val": 84 }, { "Max": 100, "StaffID": 404, "StatID": 19, "Val": 84 }, { "Max": 100, "StaffID": 404, "StatID": 20, "Val": 83 }, { "Max": 100, "StaffID": 404, "StatID": 26, "Val": 82 }, { "Max": 100, "StaffID": 404, "StatID": 27, "Val": 83 }, { "Max": 100, "StaffID": 404, "StatID": 28, "Val": 84 }, { "Max": 100, "StaffID": 404, "StatID": 29, "Val": 83 }, { "Max": 100, "StaffID": 404, "StatID": 30, "Val": 82 }, { "Max": 100, "StaffID": 404, "StatID": 31, "Val": 82 }, { "Max": 100, "StaffID": 408, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 408, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 413, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 414, "StatID": 19, "Val": 85 }, { "Max": 100, "StaffID": 414, "StatID": 20, "Val": 84 }, { "Max": 100, "StaffID": 414, "StatID": 26, "Val": 86 }, { "Max": 100, "StaffID": 414, "StatID": 27, "Val": 85 }, { "Max": 100, "StaffID": 414, "StatID": 28, "Val": 84 }, { "Max": 100, "StaffID": 414, "StatID": 29, "Val": 84 }, { "Max": 100, "StaffID": 414, "StatID": 30, "Val": 85 }, { "Max": 100, "StaffID": 414, "StatID": 31, "Val": 85 }, { "Max": 100, "StaffID": 415, "StatID": 13, "Val": 80 }, { "Max": 100, "StaffID": 415, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 415, "StatID": 43, "Val": 89 }, { "Max": 100, "StaffID": 416, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 416, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 420, "StatID": 13, "Val": 70 }, { "Max": 100, "StaffID": 420, "StatID": 25, "Val": 72 }, { "Max": 100, "StaffID": 420, "StatID": 43, "Val": 70 }, { "Max": 100, "StaffID": 421, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 421, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 422, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 422, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 422, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 423, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 423, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 423, "StatID": 43, "Val": 75 }, { "Max": 100, "StaffID": 424, "StatID": 13, "Val": 73 }, { "Max": 100, "StaffID": 424, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 424, "StatID": 43, "Val": 79 }, { "Max": 100, "StaffID": 425, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 425, "StatID": 25, "Val": 78 }, { "Max": 100, "StaffID": 425, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 426, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 426, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 426, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 427, "StatID": 25, "Val": 77 }, { "Max": 100, "StaffID": 427, "StatID": 43, "Val": 74 }, { "Max": 100, "StaffID": 428, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 428, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 428, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 429, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 429, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 429, "StatID": 43, "Val": 75 }, { "Max": 100, "StaffID": 430, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 430, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 430, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 431, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 431, "StatID": 43, "Val": 73 }, { "Max": 100, "StaffID": 432, "StatID": 13, "Val": 71 }, { "Max": 100, "StaffID": 432, "StatID": 25, "Val": 70 }, { "Max": 100, "StaffID": 432, "StatID": 43, "Val": 74 }, { "Max": 100, "StaffID": 433, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 433, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 433, "StatID": 43, "Val": 73 }, { "Max": 100, "StaffID": 434, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 434, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 435, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 435, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 435, "StatID": 43, "Val": 76 }, { "Max": 100, "StaffID": 436, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 436, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 9, "Val": 0 }, { "StaffID": 536, "StatID": 19, "Val": 79, "Max": 100 }, { "StaffID": 536, "StatID": 20, "Val": 71, "Max": 100 }, { "StaffID": 536, "StatID": 26, "Val": 77, "Max": 100 }, { "StaffID": 536, "StatID": 27, "Val": 59, "Max": 100 }, { "StaffID": 536, "StatID": 28, "Val": 59, "Max": 100 }, { "StaffID": 536, "StatID": 29, "Val": 66, "Max": 100 }, { "StaffID": 536, "StatID": 30, "Val": 70, "Max": 100 }, { "StaffID": 536, "StatID": 31, "Val": 74, "Max": 100 }, { "StaffID": 537, "StatID": 19, "Val": 72, "Max": 100 }, { "StaffID": 537, "StatID": 20, "Val": 77, "Max": 100 }, { "StaffID": 537, "StatID": 26, "Val": 68, "Max": 100 }, { "StaffID": 537, "StatID": 27, "Val": 79, "Max": 100 }, { "StaffID": 537, "StatID": 28, "Val": 79, "Max": 100 }, { "StaffID": 537, "StatID": 29, "Val": 73, "Max": 100 }, { "StaffID": 537, "StatID": 30, "Val": 65, "Max": 100 }, { "StaffID": 537, "StatID": 31, "Val": 57, "Max": 100 }, { "StaffID": 538, "StatID": 19, "Val": 63, "Max": 100 }, { "StaffID": 538, "StatID": 20, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 538, "StatID": 26, "Val": 0 }, { "StaffID": 538, "StatID": 27, "Val": 59, "Max": 100 }, { "StaffID": 538, "StatID": 28, "Val": 85, "Max": 100 }, { "StaffID": 538, "StatID": 29, "Val": 84, "Max": 100 }, { "StaffID": 538, "StatID": 30, "Val": 84, "Max": 100 }, { "StaffID": 538, "StatID": 31, "Val": 73, "Max": 100 }, { "StaffID": 539, "StatID": 0, "Val": 78, "Max": 100 }, { "StaffID": 539, "StatID": 1, "Val": 85, "Max": 100 }, { "StaffID": 539, "StatID": 14, "Val": 76, "Max": 100 }, { "StaffID": 539, "StatID": 15, "Val": 61, "Max": 100 }, { "StaffID": 539, "StatID": 16, "Val": 56, "Max": 100 }, { "StaffID": 539, "StatID": 17, "Val": 72, "Max": 100 }, { "StaffID": 540, "StatID": 0, "Val": 66, "Max": 100 }, { "StaffID": 540, "StatID": 1, "Val": 61, "Max": 100 }, { "StaffID": 540, "StatID": 14, "Val": 83, "Max": 100 }, { "StaffID": 540, "StatID": 15, "Val": 56, "Max": 100 }, { "StaffID": 540, "StatID": 16, "Val": 66, "Max": 100 }, { "StaffID": 540, "StatID": 17, "Val": 61, "Max": 100 }, { "StaffID": 541, "StatID": 0, "Val": 72, "Max": 100 }, { "StaffID": 541, "StatID": 1, "Val": 75, "Max": 100 }, { "StaffID": 541, "StatID": 14, "Val": 56, "Max": 100 }, { "StaffID": 541, "StatID": 15, "Val": 81, "Max": 100 }, { "StaffID": 541, "StatID": 16, "Val": 72, "Max": 100 }, { "StaffID": 541, "StatID": 17, "Val": 69, "Max": 100 }, { "StaffID": 542, "StatID": 11, "Val": 66, "Max": 100 }, { "StaffID": 542, "StatID": 22, "Val": 80, "Max": 100 }, { "StaffID": 542, "StatID": 23, "Val": 83, "Max": 100 }, { "StaffID": 542, "StatID": 24, "Val": 69, "Max": 100 }, { "StaffID": 543, "StatID": 11, "Val": 70, "Max": 100 }, { "StaffID": 543, "StatID": 22, "Val": 73, "Max": 100 }, { "StaffID": 543, "StatID": 23, "Val": 55, "Max": 100 }, { "StaffID": 543, "StatID": 24, "Val": 83, "Max": 100 }, { "StaffID": 544, "StatID": 11, "Val": 71, "Max": 100 }, { "StaffID": 544, "StatID": 22, "Val": 62, "Max": 100 }, { "StaffID": 544, "StatID": 23, "Val": 76, "Max": 100 }, { "StaffID": 544, "StatID": 24, "Val": 68, "Max": 100 }, { "StaffID": 545, "StatID": 11, "Val": 83, "Max": 100 }, { "StaffID": 545, "StatID": 22, "Val": 60, "Max": 100 }, { "StaffID": 545, "StatID": 23, "Val": 85, "Max": 100 }, { "StaffID": 545, "StatID": 24, "Val": 72, "Max": 100 }, { "StaffID": 546, "StatID": 11, "Val": 57, "Max": 100 }, { "StaffID": 546, "StatID": 22, "Val": 72, "Max": 100 }, { "StaffID": 546, "StatID": 23, "Val": 60, "Max": 100 }, { "StaffID": 546, "StatID": 24, "Val": 73, "Max": 100 }, { "StaffID": 547, "StatID": 2, "Val": 51, "Max": 100 }, { "StaffID": 547, "StatID": 3, "Val": 50, "Max": 100 }, { "StaffID": 547, "StatID": 4, "Val": 49, "Max": 100 }, { "StaffID": 547, "StatID": 5, "Val": 66, "Max": 100 }, { "StaffID": 547, "StatID": 6, "Val": 66, "Max": 100 }, { "StaffID": 547, "StatID": 7, "Val": 67, "Max": 100 }, { "StaffID": 547, "StatID": 8, "Val": 59, "Max": 100 }, { "StaffID": 547, "StatID": 9, "Val": 68, "Max": 100 }, { "StaffID": 547, "StatID": 10, "Val": 58, "Max": 100 }, { "StaffID": 548, "StatID": 2, "Val": 66, "Max": 100 }, { "StaffID": 548, "StatID": 3, "Val": 48, "Max": 100 }, { "StaffID": 548, "StatID": 4, "Val": 67, "Max": 100 }, { "StaffID": 548, "StatID": 5, "Val": 62, "Max": 100 }, { "StaffID": 548, "StatID": 6, "Val": 47, "Max": 100 }, { "StaffID": 548, "StatID": 7, "Val": 53, "Max": 100 }, { "StaffID": 548, "StatID": 8, "Val": 50, "Max": 100 }, { "StaffID": 548, "StatID": 9, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 548, "StatID": 10, "Val": 0 }, { "StaffID": 549, "StatID": 2, "Val": 62, "Max": 100 }, { "StaffID": 549, "StatID": 3, "Val": 57, "Max": 100 }, { "StaffID": 549, "StatID": 4, "Val": 51, "Max": 100 }, { "StaffID": 549, "StatID": 5, "Val": 55, "Max": 100 }, { "StaffID": 549, "StatID": 6, "Val": 58, "Max": 100 }, { "StaffID": 549, "StatID": 7, "Val": 62, "Max": 100 }, { "StaffID": 549, "StatID": 8, "Val": 60, "Max": 100 }, { "StaffID": 549, "StatID": 9, "Val": 67, "Max": 100 }, { "StaffID": 549, "StatID": 10, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 550, "StatID": 2, "Val": 0 }, { "StaffID": 550, "StatID": 3, "Val": 69, "Max": 100 }, { "StaffID": 550, "StatID": 4, "Val": 57, "Max": 100 }, { "StaffID": 550, "StatID": 5, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 550, "StatID": 6, "Val": 0 }, { "StaffID": 550, "StatID": 7, "Val": 48, "Max": 100 }, { "StaffID": 550, "StatID": 8, "Val": 53, "Max": 100 }, { "StaffID": 550, "StatID": 9, "Val": 51, "Max": 100 }, { "StaffID": 550, "StatID": 10, "Val": 54, "Max": 100 }, { "StaffID": 551, "StatID": 2, "Val": 68, "Max": 100 }, { "StaffID": 551, "StatID": 3, "Val": 54, "Max": 100 }, { "StaffID": 551, "StatID": 4, "Val": 51, "Max": 100 }, { "StaffID": 551, "StatID": 5, "Val": 55, "Max": 100 }, { "StaffID": 551, "StatID": 6, "Val": 69, "Max": 100 }, { "StaffID": 551, "StatID": 7, "Val": 56, "Max": 100 }, { "StaffID": 551, "StatID": 8, "Val": 63, "Max": 100 }, { "StaffID": 551, "StatID": 9, "Val": 66, "Max": 100 }, { "StaffID": 551, "StatID": 10, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 554, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 554, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 555, "StatID": 13, "Val": 67 }, { "Max": 100, "StaffID": 555, "StatID": 25, "Val": 67 }, { "Max": 100, "StaffID": 555, "StatID": 43, "Val": 67 }, { "Max": 100, "StaffID": 556, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 556, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 556, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 557, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 557, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 557, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 557, "StatID": 24, "Val": 0 }, { "StaffID": 567, "StatID": 0, "Val": 56, "Max": 100 }, { "StaffID": 567, "StatID": 1, "Val": 73, "Max": 100 }, { "StaffID": 567, "StatID": 14, "Val": 64, "Max": 100 }, { "StaffID": 567, "StatID": 15, "Val": 82, "Max": 100 }, { "StaffID": 567, "StatID": 16, "Val": 77, "Max": 100 }, { "StaffID": 567, "StatID": 17, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 568, "StatID": 19, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 20, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 26, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 27, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 28, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 29, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 30, "Val": 0 }, { "Max": 100, "StaffID": 568, "StatID": 31, "Val": 0 }, { "Max": 100, "StaffID": 146, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 153, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 173, "StatID": 43, "Val": 77 }, { "Max": 100, "StaffID": 178, "StatID": 13, "Val": 78 }, { "Max": 100, "StaffID": 181, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 312, "StatID": 23, "Val": 74 }, { "Max": 100, "StaffID": 436, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 437, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 438, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 608, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 622, "StatID": 13, "Val": 86 }, { "Max": 100, "StaffID": 622, "StatID": 25, "Val": 87 }, { "Max": 100, "StaffID": 622, "StatID": 43, "Val": 86 }, { "Max": 100, "StaffID": 623, "StatID": 13, "Val": 87 }, { "Max": 100, "StaffID": 623, "StatID": 25, "Val": 86 }, { "Max": 100, "StaffID": 623, "StatID": 43, "Val": 88 }, { "Max": 100, "StaffID": 624, "StatID": 13, "Val": 80 }, { "Max": 100, "StaffID": 624, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 624, "StatID": 43, "Val": 79 }, { "Max": 100, "StaffID": 625, "StatID": 13, "Val": 85 }, { "Max": 100, "StaffID": 625, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 625, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 626, "StatID": 13, "Val": 82 }, { "Max": 100, "StaffID": 626, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 626, "StatID": 43, "Val": 81 }, { "Max": 100, "StaffID": 628, "StatID": 0, "Val": 90 }, { "Max": 100, "StaffID": 628, "StatID": 1, "Val": 89 }, { "Max": 100, "StaffID": 628, "StatID": 14, "Val": 88 }, { "Max": 100, "StaffID": 628, "StatID": 15, "Val": 89 }, { "Max": 100, "StaffID": 628, "StatID": 16, "Val": 91 }, { "Max": 100, "StaffID": 628, "StatID": 17, "Val": 88 }, { "Max": 100, "StaffID": 630, "StatID": 0, "Val": 85 }, { "Max": 100, "StaffID": 630, "StatID": 1, "Val": 85 }, { "Max": 100, "StaffID": 630, "StatID": 14, "Val": 84 }, { "Max": 100, "StaffID": 630, "StatID": 15, "Val": 84 }, { "Max": 100, "StaffID": 630, "StatID": 16, "Val": 88 }, { "Max": 100, "StaffID": 630, "StatID": 17, "Val": 84 }, { "Max": 100, "StaffID": 633, "StatID": 0, "Val": 80 }, { "Max": 100, "StaffID": 633, "StatID": 1, "Val": 81 }, { "Max": 100, "StaffID": 633, "StatID": 14, "Val": 80 }, { "Max": 100, "StaffID": 633, "StatID": 15, "Val": 79 }, { "Max": 100, "StaffID": 633, "StatID": 16, "Val": 81 }, { "Max": 100, "StaffID": 633, "StatID": 17, "Val": 80 }, { "Max": 100, "StaffID": 635, "StatID": 19, "Val": 87 }, { "Max": 100, "StaffID": 635, "StatID": 20, "Val": 92 }, { "Max": 100, "StaffID": 635, "StatID": 26, "Val": 92 }, { "Max": 100, "StaffID": 635, "StatID": 27, "Val": 91 }, { "Max": 100, "StaffID": 635, "StatID": 28, "Val": 90 }, { "Max": 100, "StaffID": 635, "StatID": 29, "Val": 88 }, { "Max": 100, "StaffID": 635, "StatID": 30, "Val": 91 }, { "Max": 100, "StaffID": 635, "StatID": 31, "Val": 89 }, { "Max": 100, "StaffID": 636, "StatID": 19, "Val": 83 }, { "Max": 100, "StaffID": 636, "StatID": 20, "Val": 82 }, { "Max": 100, "StaffID": 636, "StatID": 26, "Val": 83 }, { "Max": 100, "StaffID": 636, "StatID": 27, "Val": 84 }, { "Max": 100, "StaffID": 636, "StatID": 28, "Val": 83 }, { "Max": 100, "StaffID": 636, "StatID": 29, "Val": 81 }, { "Max": 100, "StaffID": 636, "StatID": 30, "Val": 84 }, { "Max": 100, "StaffID": 636, "StatID": 31, "Val": 83 }, { "Max": 100, "StaffID": 637, "StatID": 19, "Val": 87 }, { "Max": 100, "StaffID": 637, "StatID": 20, "Val": 88 }, { "Max": 100, "StaffID": 637, "StatID": 26, "Val": 89 }, { "Max": 100, "StaffID": 637, "StatID": 27, "Val": 88 }, { "Max": 100, "StaffID": 637, "StatID": 28, "Val": 87 }, { "Max": 100, "StaffID": 637, "StatID": 29, "Val": 88 }, { "Max": 100, "StaffID": 637, "StatID": 30, "Val": 87 }, { "Max": 100, "StaffID": 637, "StatID": 31, "Val": 88 }, { "Max": 100, "StaffID": 638, "StatID": 11, "Val": 85 }, { "Max": 100, "StaffID": 638, "StatID": 22, "Val": 92 }, { "Max": 100, "StaffID": 638, "StatID": 23, "Val": 86 }, { "Max": 100, "StaffID": 638, "StatID": 24, "Val": 88 }, { "Max": 100, "StaffID": 639, "StatID": 11, "Val": 82 }, { "Max": 100, "StaffID": 639, "StatID": 22, "Val": 85 }, { "Max": 100, "StaffID": 639, "StatID": 23, "Val": 83 }, { "Max": 100, "StaffID": 639, "StatID": 24, "Val": 90 }, { "Max": 100, "StaffID": 640, "StatID": 11, "Val": 82 }, { "Max": 100, "StaffID": 640, "StatID": 22, "Val": 84 }, { "Max": 100, "StaffID": 640, "StatID": 23, "Val": 84 }, { "Max": 100, "StaffID": 640, "StatID": 24, "Val": 85 }, { "Max": 100, "StaffID": 641, "StatID": 11, "Val": 88 }, { "Max": 100, "StaffID": 641, "StatID": 22, "Val": 93 }, { "Max": 100, "StaffID": 641, "StatID": 23, "Val": 88 }, { "Max": 100, "StaffID": 641, "StatID": 24, "Val": 90 }, { "Max": 100, "StaffID": 642, "StatID": 0, "Val": 90 }, { "Max": 100, "StaffID": 642, "StatID": 1, "Val": 88 }, { "Max": 100, "StaffID": 642, "StatID": 14, "Val": 89 }, { "Max": 100, "StaffID": 642, "StatID": 15, "Val": 88 }, { "Max": 100, "StaffID": 642, "StatID": 16, "Val": 89 }, { "Max": 100, "StaffID": 642, "StatID": 17, "Val": 91 }, { "Max": 100, "StaffID": 643, "StatID": 13, "Val": 86 }, { "Max": 100, "StaffID": 643, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 643, "StatID": 43, "Val": 81 }, { "Max": 100, "StaffID": 644, "StatID": 13, "Val": 80 }, { "Max": 100, "StaffID": 644, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 644, "StatID": 43, "Val": 85 }, { "Max": 100, "StaffID": 40, "StatID": 19, "Val": 88 }, { "Max": 100, "StaffID": 33, "StatID": 0, "Val": 84 }, { "Max": 100, "StaffID": 58, "StatID": 13, "Val": 83 }, { "Max": 100, "StaffID": 119, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 158, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 159, "StatID": 13, "Val": 80 }, { "Max": 100, "StaffID": 193, "StatID": 13, "Val": 72 }, { "Max": 100, "StaffID": 313, "StatID": 24, "Val": 73 }, { "Max": 100, "StaffID": 341, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 361, "StatID": 25, "Val": 74 }, { "Max": 100, "StaffID": 386, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 388, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 421, "StatID": 13, "Val": 75 }, { "Max": 100, "StaffID": 427, "StatID": 13, "Val": 76 }, { "Max": 100, "StaffID": 431, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 434, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 677, "StatID": 19, "Val": 88 }, { "Max": 100, "StaffID": 677, "StatID": 20, "Val": 90 }, { "Max": 100, "StaffID": 677, "StatID": 26, "Val": 92 }, { "Max": 100, "StaffID": 677, "StatID": 27, "Val": 91 }, { "Max": 100, "StaffID": 677, "StatID": 28, "Val": 89 }, { "Max": 100, "StaffID": 677, "StatID": 29, "Val": 90 }, { "Max": 100, "StaffID": 677, "StatID": 30, "Val": 91 }, { "Max": 100, "StaffID": 677, "StatID": 31, "Val": 90 }, { "Max": 100, "StaffID": 678, "StatID": 11, "Val": 84 }, { "Max": 100, "StaffID": 678, "StatID": 22, "Val": 90 }, { "Max": 100, "StaffID": 678, "StatID": 23, "Val": 84 }, { "Max": 100, "StaffID": 678, "StatID": 24, "Val": 89 }, { "Max": 100, "StaffID": 679, "StatID": 11, "Val": 82 }, { "Max": 100, "StaffID": 679, "StatID": 22, "Val": 82 }, { "Max": 100, "StaffID": 679, "StatID": 23, "Val": 86 }, { "Max": 100, "StaffID": 679, "StatID": 24, "Val": 82 }, { "Max": 100, "StaffID": 680, "StatID": 13, "Val": 84 }, { "Max": 100, "StaffID": 680, "StatID": 25, "Val": 84 }, { "Max": 100, "StaffID": 680, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 681, "StatID": 0, "Val": 88 }, { "Max": 100, "StaffID": 681, "StatID": 1, "Val": 86 }, { "Max": 100, "StaffID": 681, "StatID": 14, "Val": 85 }, { "Max": 100, "StaffID": 681, "StatID": 15, "Val": 87 }, { "Max": 100, "StaffID": 681, "StatID": 16, "Val": 87 }, { "Max": 100, "StaffID": 681, "StatID": 17, "Val": 87 }, { "Max": 100, "StaffID": 682, "StatID": 13, "Val": 84 }, { "Max": 100, "StaffID": 682, "StatID": 25, "Val": 82 }, { "Max": 100, "StaffID": 682, "StatID": 43, "Val": 83 }, { "Max": 100, "StaffID": 683, "StatID": 0, "Val": 87 }, { "Max": 100, "StaffID": 683, "StatID": 1, "Val": 85 }, { "Max": 100, "StaffID": 683, "StatID": 14, "Val": 84 }, { "Max": 100, "StaffID": 683, "StatID": 15, "Val": 85 }, { "Max": 100, "StaffID": 683, "StatID": 16, "Val": 86 }, { "Max": 100, "StaffID": 683, "StatID": 17, "Val": 87 }, { "Max": 100, "StaffID": 684, "StatID": 13, "Val": 83 }, { "Max": 100, "StaffID": 684, "StatID": 25, "Val": 83 }, { "Max": 100, "StaffID": 684, "StatID": 43, "Val": 84 }, { "Max": 100, "StaffID": 395, "StatID": 13, "Val": 86 }, { "Max": 100, "StaffID": 395, "StatID": 25, "Val": 87 }, { "Max": 100, "StaffID": 395, "StatID": 43, "Val": 86 }, { "Max": 100, "StaffID": 338, "StatID": 13, "Val": 72 }, { "Max": 100, "StaffID": 338, "StatID": 25, "Val": 76 }, { "Max": 100, "StaffID": 338, "StatID": 43, "Val": 72 }, { "Max": 100, "StaffID": 156, "StatID": 13, "Val": 78 }, { "Max": 100, "StaffID": 156, "StatID": 25, "Val": 79 }, { "Max": 100, "StaffID": 156, "StatID": 43, "Val": 78 }, { "Max": 100, "StaffID": 145, "StatID": 13, "Val": 74 }, { "Max": 100, "StaffID": 145, "StatID": 25, "Val": 75 }, { "Max": 100, "StaffID": 145, "StatID": 43, "Val": 74 }, { "Max": 100, "StaffID": 685, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 685, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 686, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 687, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 688, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 689, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 690, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 690, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 690, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 691, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 691, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 691, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 692, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 692, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 692, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 693, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 693, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 693, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 694, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 694, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 694, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 695, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 695, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 695, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 0, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 1, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 14, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 15, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 16, "Val": 0 }, { "Max": 100, "StaffID": 696, "StatID": 17, "Val": 0 }, { "Max": 100, "StaffID": 697, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 697, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 697, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 697, "StatID": 24, "Val": 0 }, { "Max": 100, "StaffID": 698, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 698, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 698, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 698, "StatID": 24, "Val": 0 }, { "Max": 100, "StaffID": 699, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 699, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 699, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 699, "StatID": 24, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 0, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 1, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 14, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 15, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 16, "Val": 0 }, { "Max": 100, "StaffID": 700, "StatID": 17, "Val": 0 }, { "Max": 100, "StaffID": 701, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 701, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 701, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 702, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 702, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 702, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 19, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 20, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 26, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 27, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 28, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 29, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 30, "Val": 0 }, { "Max": 100, "StaffID": 703, "StatID": 31, "Val": 0 }, { "Max": 100, "StaffID": 704, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 704, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 704, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 704, "StatID": 24, "Val": 0 }, { "Max": 100, "StaffID": 705, "StatID": 11, "Val": 0 }, { "Max": 100, "StaffID": 705, "StatID": 22, "Val": 0 }, { "Max": 100, "StaffID": 705, "StatID": 23, "Val": 0 }, { "Max": 100, "StaffID": 705, "StatID": 24, "Val": 0 }, { "Max": 100, "StaffID": 706, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 706, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 706, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 0, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 1, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 14, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 15, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 16, "Val": 0 }, { "Max": 100, "StaffID": 707, "StatID": 17, "Val": 0 }, { "Max": 100, "StaffID": 708, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 708, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 708, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 0, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 1, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 14, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 15, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 16, "Val": 0 }, { "Max": 100, "StaffID": 709, "StatID": 17, "Val": 0 }, { "Max": 100, "StaffID": 710, "StatID": 13, "Val": 0 }, { "Max": 100, "StaffID": 710, "StatID": 25, "Val": 0 }, { "Max": 100, "StaffID": 710, "StatID": 43, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 711, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 712, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 713, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 714, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 715, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 716, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 43, "StatID": 26, "Val": 85 }, { "Max": 100, "StaffID": 10, "StatID": 2, "Val": 96 }, { "Max": 100, "StaffID": 10, "StatID": 3, "Val": 94 }, { "Max": 100, "StaffID": 10, "StatID": 4, "Val": 95 }, { "Max": 100, "StaffID": 10, "StatID": 5, "Val": 93 }, { "Max": 100, "StaffID": 10, "StatID": 6, "Val": 92 }, { "Max": 100, "StaffID": 10, "StatID": 7, "Val": 92 }, { "Max": 100, "StaffID": 10, "StatID": 8, "Val": 93 }, { "Max": 100, "StaffID": 10, "StatID": 9, "Val": 95 }, { "Max": 100, "StaffID": 10, "StatID": 10, "Val": 92 }, { "Max": 100, "StaffID": 2, "StatID": 2, "Val": 93 }, { "Max": 100, "StaffID": 2, "StatID": 3, "Val": 95 }, { "Max": 100, "StaffID": 2, "StatID": 4, "Val": 88 }, { "Max": 100, "StaffID": 2, "StatID": 5, "Val": 86 }, { "Max": 100, "StaffID": 2, "StatID": 6, "Val": 88 }, { "Max": 100, "StaffID": 2, "StatID": 7, "Val": 92 }, { "Max": 100, "StaffID": 2, "StatID": 8, "Val": 88 }, { "Max": 100, "StaffID": 2, "StatID": 9, "Val": 92 }, { "StaffID": 2, "StatID": 10, "Val": 88, "Max": 100 }, { "Max": 100, "StaffID": 23, "StatID": 2, "Val": 90 }, { "Max": 100, "StaffID": 23, "StatID": 3, "Val": 94 }, { "Max": 100, "StaffID": 23, "StatID": 4, "Val": 87 }, { "StaffID": 23, "StatID": 5, "Val": 86, "Max": 100 }, { "Max": 100, "StaffID": 23, "StatID": 6, "Val": 91 }, { "Max": 100, "StaffID": 23, "StatID": 7, "Val": 85 }, { "Max": 100, "StaffID": 23, "StatID": 8, "Val": 89 }, { "StaffID": 23, "StatID": 9, "Val": 93, "Max": 100 }, { "StaffID": 23, "StatID": 10, "Val": 94, "Max": 100 }, { "Max": 100, "StaffID": 12, "StatID": 2, "Val": 90 }, { "Max": 100, "StaffID": 12, "StatID": 3, "Val": 88 }, { "Max": 100, "StaffID": 12, "StatID": 4, "Val": 88 }, { "Max": 100, "StaffID": 12, "StatID": 5, "Val": 93 }, { "Max": 100, "StaffID": 12, "StatID": 6, "Val": 90 }, { "Max": 100, "StaffID": 12, "StatID": 7, "Val": 93 }, { "Max": 100, "StaffID": 12, "StatID": 8, "Val": 84 }, { "Max": 100, "StaffID": 12, "StatID": 9, "Val": 91 }, { "Max": 100, "StaffID": 12, "StatID": 10, "Val": 89 }, { "Max": 100, "StaffID": 77, "StatID": 2, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 3, "Val": 88 }, { "Max": 100, "StaffID": 77, "StatID": 4, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 5, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 6, "Val": 91 }, { "Max": 100, "StaffID": 77, "StatID": 7, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 8, "Val": 90 }, { "Max": 100, "StaffID": 77, "StatID": 9, "Val": 88 }, { "Max": 100, "StaffID": 77, "StatID": 10, "Val": 87 }, { "Max": 100, "StaffID": 102, "StatID": 2, "Val": 87 }, { "Max": 100, "StaffID": 102, "StatID": 3, "Val": 86 }, { "Max": 100, "StaffID": 102, "StatID": 4, "Val": 90 }, { "Max": 100, "StaffID": 102, "StatID": 5, "Val": 91 }, { "Max": 100, "StaffID": 102, "StatID": 6, "Val": 88 }, { "Max": 100, "StaffID": 102, "StatID": 7, "Val": 89 }, { "Max": 100, "StaffID": 102, "StatID": 8, "Val": 86 }, { "Max": 100, "StaffID": 102, "StatID": 9, "Val": 88 }, { "Max": 100, "StaffID": 102, "StatID": 10, "Val": 89 }, { "Max": 100, "StaffID": 1, "StatID": 2, "Val": 87 }, { "StaffID": 1, "StatID": 3, "Val": 87, "Max": 100 }, { "Max": 100, "StaffID": 1, "StatID": 4, "Val": 89 }, { "Max": 100, "StaffID": 1, "StatID": 5, "Val": 94 }, { "Max": 100, "StaffID": 1, "StatID": 6, "Val": 93 }, { "Max": 100, "StaffID": 1, "StatID": 7, "Val": 87 }, { "Max": 100, "StaffID": 1, "StatID": 8, "Val": 84 }, { "StaffID": 1, "StatID": 9, "Val": 88, "Max": 100 }, { "StaffID": 1, "StatID": 10, "Val": 89, "Max": 100 }, { "StaffID": 11, "StatID": 2, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 11, "StatID": 3, "Val": 87 }, { "Max": 100, "StaffID": 11, "StatID": 4, "Val": 88 }, { "Max": 100, "StaffID": 11, "StatID": 5, "Val": 90 }, { "Max": 100, "StaffID": 11, "StatID": 6, "Val": 87 }, { "StaffID": 11, "StatID": 7, "Val": 83, "Max": 100 }, { "Max": 100, "StaffID": 11, "StatID": 8, "Val": 84 }, { "Max": 100, "StaffID": 11, "StatID": 9, "Val": 84 }, { "Max": 100, "StaffID": 11, "StatID": 10, "Val": 88 }, { "StaffID": 15, "StatID": 2, "Val": 89, "Max": 100 }, { "Max": 100, "StaffID": 15, "StatID": 3, "Val": 88 }, { "Max": 100, "StaffID": 15, "StatID": 4, "Val": 83 }, { "Max": 100, "StaffID": 15, "StatID": 5, "Val": 82 }, { "Max": 100, "StaffID": 15, "StatID": 6, "Val": 85 }, { "Max": 100, "StaffID": 15, "StatID": 7, "Val": 85 }, { "Max": 100, "StaffID": 15, "StatID": 8, "Val": 84 }, { "StaffID": 15, "StatID": 9, "Val": 84, "Max": 100 }, { "StaffID": 15, "StatID": 10, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 3, "StatID": 2, "Val": 84 }, { "Max": 100, "StaffID": 3, "StatID": 3, "Val": 83 }, { "Max": 100, "StaffID": 3, "StatID": 4, "Val": 87 }, { "Max": 100, "StaffID": 3, "StatID": 5, "Val": 86 }, { "Max": 100, "StaffID": 3, "StatID": 6, "Val": 90 }, { "Max": 100, "StaffID": 3, "StatID": 7, "Val": 84 }, { "Max": 100, "StaffID": 3, "StatID": 8, "Val": 84 }, { "Max": 100, "StaffID": 3, "StatID": 9, "Val": 85 }, { "Max": 100, "StaffID": 3, "StatID": 10, "Val": 84 }, { "Max": 100, "StaffID": 83, "StatID": 2, "Val": 84 }, { "Max": 100, "StaffID": 83, "StatID": 3, "Val": 88 }, { "Max": 100, "StaffID": 83, "StatID": 4, "Val": 84 }, { "Max": 100, "StaffID": 83, "StatID": 5, "Val": 84 }, { "Max": 100, "StaffID": 83, "StatID": 6, "Val": 85 }, { "Max": 100, "StaffID": 83, "StatID": 7, "Val": 80 }, { "Max": 100, "StaffID": 83, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 83, "StatID": 9, "Val": 83 }, { "StaffID": 83, "StatID": 10, "Val": 87, "Max": 100 }, { "StaffID": 376, "StatID": 2, "Val": 90, "Max": 100 }, { "StaffID": 376, "StatID": 3, "Val": 88, "Max": 100 }, { "StaffID": 376, "StatID": 4, "Val": 84, "Max": 100 }, { "StaffID": 376, "StatID": 5, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 376, "StatID": 6, "Val": 86 }, { "StaffID": 376, "StatID": 7, "Val": 85, "Max": 100 }, { "Max": 100, "StaffID": 376, "StatID": 8, "Val": 80 }, { "StaffID": 376, "StatID": 9, "Val": 91, "Max": 100 }, { "StaffID": 376, "StatID": 10, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 17, "StatID": 2, "Val": 82 }, { "Max": 100, "StaffID": 17, "StatID": 3, "Val": 82 }, { "Max": 100, "StaffID": 17, "StatID": 4, "Val": 84 }, { "Max": 100, "StaffID": 17, "StatID": 5, "Val": 88 }, { "Max": 100, "StaffID": 17, "StatID": 6, "Val": 86 }, { "Max": 100, "StaffID": 17, "StatID": 7, "Val": 81 }, { "Max": 100, "StaffID": 17, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 17, "StatID": 9, "Val": 82 }, { "Max": 100, "StaffID": 17, "StatID": 10, "Val": 82 }, { "StaffID": 144, "StatID": 2, "Val": 88, "Max": 100 }, { "StaffID": 144, "StatID": 3, "Val": 85, "Max": 100 }, { "Max": 100, "StaffID": 144, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 144, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 144, "StatID": 6, "Val": 84 }, { "StaffID": 144, "StatID": 7, "Val": 89, "Max": 100 }, { "Max": 100, "StaffID": 144, "StatID": 8, "Val": 80 }, { "StaffID": 144, "StatID": 9, "Val": 90, "Max": 100 }, { "StaffID": 144, "StatID": 10, "Val": 81, "Max": 100 }, { "Max": 100, "StaffID": 8, "StatID": 2, "Val": 82 }, { "Max": 100, "StaffID": 8, "StatID": 3, "Val": 87 }, { "Max": 100, "StaffID": 8, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 8, "StatID": 5, "Val": 84 }, { "Max": 100, "StaffID": 8, "StatID": 6, "Val": 82 }, { "Max": 100, "StaffID": 8, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 8, "StatID": 8, "Val": 76 }, { "Max": 100, "StaffID": 8, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 8, "StatID": 10, "Val": 87 }, { "Max": 100, "StaffID": 81, "StatID": 2, "Val": 85 }, { "Max": 100, "StaffID": 81, "StatID": 3, "Val": 84 }, { "Max": 100, "StaffID": 81, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 81, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 81, "StatID": 6, "Val": 84 }, { "Max": 100, "StaffID": 81, "StatID": 7, "Val": 86 }, { "Max": 100, "StaffID": 81, "StatID": 8, "Val": 83 }, { "Max": 100, "StaffID": 81, "StatID": 9, "Val": 85 }, { "Max": 100, "StaffID": 81, "StatID": 10, "Val": 79 }, { "StaffID": 14, "StatID": 2, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 14, "StatID": 3, "Val": 82 }, { "StaffID": 14, "StatID": 4, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 14, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 14, "StatID": 6, "Val": 83 }, { "Max": 100, "StaffID": 14, "StatID": 7, "Val": 80 }, { "Max": 100, "StaffID": 14, "StatID": 8, "Val": 88 }, { "Max": 100, "StaffID": 14, "StatID": 9, "Val": 82 }, { "StaffID": 14, "StatID": 10, "Val": 81, "Max": 100 }, { "StaffID": 142, "StatID": 2, "Val": 86, "Max": 100 }, { "StaffID": 142, "StatID": 3, "Val": 84, "Max": 100 }, { "StaffID": 142, "StatID": 4, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 142, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 142, "StatID": 6, "Val": 82 }, { "StaffID": 142, "StatID": 7, "Val": 88, "Max": 100 }, { "StaffID": 142, "StatID": 8, "Val": 81, "Max": 100 }, { "StaffID": 142, "StatID": 9, "Val": 85, "Max": 100 }, { "StaffID": 142, "StatID": 10, "Val": 83, "Max": 100 }, { "StaffID": 279, "StatID": 2, "Val": 83, "Max": 100 }, { "StaffID": 279, "StatID": 3, "Val": 83, "Max": 100 }, { "Max": 100, "StaffID": 279, "StatID": 4, "Val": 82 }, { "Max": 100, "StaffID": 279, "StatID": 5, "Val": 83 }, { "StaffID": 279, "StatID": 6, "Val": 79, "Max": 100 }, { "StaffID": 279, "StatID": 7, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 279, "StatID": 8, "Val": 78 }, { "Max": 100, "StaffID": 279, "StatID": 9, "Val": 83 }, { "StaffID": 279, "StatID": 10, "Val": 85, "Max": 100 }, { "Max": 100, "StaffID": 95, "StatID": 2, "Val": 83 }, { "Max": 100, "StaffID": 95, "StatID": 3, "Val": 82 }, { "StaffID": 95, "StatID": 4, "Val": 81, "Max": 100 }, { "Max": 100, "StaffID": 95, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 95, "StatID": 6, "Val": 83 }, { "StaffID": 95, "StatID": 7, "Val": 87, "Max": 100 }, { "Max": 100, "StaffID": 95, "StatID": 8, "Val": 86 }, { "StaffID": 95, "StatID": 9, "Val": 84, "Max": 100 }, { "StaffID": 95, "StatID": 10, "Val": 81, "Max": 100 }, { "Max": 100, "StaffID": 18, "StatID": 2, "Val": 81 }, { "Max": 100, "StaffID": 18, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 18, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 18, "StatID": 5, "Val": 83 }, { "Max": 100, "StaffID": 18, "StatID": 6, "Val": 83 }, { "Max": 100, "StaffID": 18, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 18, "StatID": 8, "Val": 80 }, { "Max": 100, "StaffID": 18, "StatID": 9, "Val": 79 }, { "Max": 100, "StaffID": 18, "StatID": 10, "Val": 79 }, { "Max": 100, "StaffID": 248, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 248, "StatID": 3, "Val": 80 }, { "Max": 100, "StaffID": 248, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 248, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 248, "StatID": 6, "Val": 82 }, { "StaffID": 248, "StatID": 7, "Val": 85, "Max": 100 }, { "Max": 100, "StaffID": 248, "StatID": 8, "Val": 78 }, { "StaffID": 248, "StatID": 9, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 248, "StatID": 10, "Val": 77 }, { "Max": 100, "StaffID": 255, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 255, "StatID": 3, "Val": 81 }, { "Max": 100, "StaffID": 255, "StatID": 4, "Val": 78 }, { "Max": 100, "StaffID": 255, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 255, "StatID": 6, "Val": 78 }, { "StaffID": 255, "StatID": 7, "Val": 83, "Max": 100 }, { "StaffID": 255, "StatID": 8, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 255, "StatID": 9, "Val": 80 }, { "StaffID": 255, "StatID": 10, "Val": 77, "Max": 100 }, { "Max": 100, "StaffID": 20, "StatID": 2, "Val": 76 }, { "Max": 100, "StaffID": 20, "StatID": 3, "Val": 80 }, { "Max": 100, "StaffID": 20, "StatID": 4, "Val": 78 }, { "Max": 100, "StaffID": 20, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 20, "StatID": 6, "Val": 77 }, { "StaffID": 20, "StatID": 7, "Val": 76, "Max": 100 }, { "Max": 100, "StaffID": 20, "StatID": 8, "Val": 77 }, { "Max": 100, "StaffID": 20, "StatID": 9, "Val": 77 }, { "StaffID": 20, "StatID": 10, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 285, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 285, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 285, "StatID": 4, "Val": 76 }, { "Max": 100, "StaffID": 285, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 285, "StatID": 6, "Val": 76 }, { "StaffID": 285, "StatID": 7, "Val": 84, "Max": 100 }, { "Max": 100, "StaffID": 285, "StatID": 8, "Val": 78 }, { "Max": 100, "StaffID": 285, "StatID": 9, "Val": 81 }, { "Max": 100, "StaffID": 285, "StatID": 10, "Val": 75 }, { "Max": 100, "StaffID": 87, "StatID": 2, "Val": 76 }, { "Max": 100, "StaffID": 87, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 87, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 87, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 87, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 87, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 87, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 87, "StatID": 9, "Val": 76 }, { "StaffID": 87, "StatID": 10, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 106, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 106, "StatID": 3, "Val": 80 }, { "Max": 100, "StaffID": 106, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 106, "StatID": 5, "Val": 76 }, { "Max": 100, "StaffID": 106, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 106, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 106, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 106, "StatID": 9, "Val": 76 }, { "StaffID": 106, "StatID": 10, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 105, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 105, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 4, "Val": 80 }, { "Max": 100, "StaffID": 105, "StatID": 5, "Val": 82 }, { "Max": 100, "StaffID": 105, "StatID": 6, "Val": 79 }, { "Max": 100, "StaffID": 105, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 105, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 9, "Val": 75 }, { "Max": 100, "StaffID": 105, "StatID": 10, "Val": 79 }, { "Max": 100, "StaffID": 281, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 281, "StatID": 3, "Val": 76 }, { "Max": 100, "StaffID": 281, "StatID": 4, "Val": 79 }, { "Max": 100, "StaffID": 281, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 281, "StatID": 6, "Val": 77 }, { "Max": 100, "StaffID": 281, "StatID": 7, "Val": 75 }, { "Max": 100, "StaffID": 281, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 281, "StatID": 9, "Val": 76 }, { "Max": 100, "StaffID": 281, "StatID": 10, "Val": 78 }, { "Max": 100, "StaffID": 120, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 120, "StatID": 3, "Val": 80 }, { "Max": 100, "StaffID": 120, "StatID": 4, "Val": 76 }, { "Max": 100, "StaffID": 120, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 120, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 120, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 120, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 120, "StatID": 9, "Val": 78 }, { "Max": 100, "StaffID": 120, "StatID": 10, "Val": 75 }, { "StaffID": 645, "StatID": 2, "Val": 74, "Max": 100 }, { "StaffID": 645, "StatID": 3, "Val": 72, "Max": 100 }, { "StaffID": 645, "StatID": 4, "Val": 69, "Max": 100 }, { "StaffID": 645, "StatID": 5, "Val": 69, "Max": 100 }, { "StaffID": 645, "StatID": 6, "Val": 73, "Max": 100 }, { "StaffID": 645, "StatID": 7, "Val": 80, "Max": 100 }, { "StaffID": 645, "StatID": 8, "Val": 72, "Max": 100 }, { "StaffID": 645, "StatID": 9, "Val": 76, "Max": 100 }, { "StaffID": 645, "StatID": 10, "Val": 69, "Max": 100 }, { "Max": 100, "StaffID": 76, "StatID": 2, "Val": 73 }, { "StaffID": 76, "StatID": 3, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 76, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 76, "StatID": 5, "Val": 76 }, { "Max": 100, "StaffID": 76, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 76, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 76, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 76, "StatID": 9, "Val": 74 }, { "StaffID": 76, "StatID": 10, "Val": 78, "Max": 100 }, { "StaffID": 373, "StatID": 2, "Val": 80, "Max": 100 }, { "StaffID": 373, "StatID": 3, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 373, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 373, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 373, "StatID": 6, "Val": 75 }, { "StaffID": 373, "StatID": 7, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 373, "StatID": 8, "Val": 73 }, { "StaffID": 373, "StatID": 9, "Val": 83, "Max": 100 }, { "StaffID": 373, "StatID": 10, "Val": 74, "Max": 100 }, { "Max": 100, "StaffID": 107, "StatID": 2, "Val": 76 }, { "Max": 100, "StaffID": 107, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 107, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 107, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 107, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 107, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 107, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 107, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 107, "StatID": 10, "Val": 77 }, { "Max": 100, "StaffID": 135, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 135, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 135, "StatID": 7, "Val": 75 }, { "Max": 100, "StaffID": 135, "StatID": 8, "Val": 79 }, { "Max": 100, "StaffID": 135, "StatID": 9, "Val": 76 }, { "Max": 100, "StaffID": 135, "StatID": 10, "Val": 76 }, { "Max": 100, "StaffID": 22, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 22, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 22, "StatID": 4, "Val": 78 }, { "Max": 100, "StaffID": 22, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 22, "StatID": 6, "Val": 78 }, { "Max": 100, "StaffID": 22, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 22, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 22, "StatID": 9, "Val": 73 }, { "StaffID": 22, "StatID": 10, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 398, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 398, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 398, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 398, "StatID": 5, "Val": 81 }, { "Max": 100, "StaffID": 398, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 398, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 398, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 398, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 398, "StatID": 10, "Val": 78 }, { "Max": 100, "StaffID": 74, "StatID": 2, "Val": 77 }, { "Max": 100, "StaffID": 74, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 74, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 74, "StatID": 5, "Val": 74 }, { "Max": 100, "StaffID": 74, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 74, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 74, "StatID": 8, "Val": 75 }, { "Max": 100, "StaffID": 74, "StatID": 9, "Val": 80 }, { "Max": 100, "StaffID": 74, "StatID": 10, "Val": 74 }, { "Max": 100, "StaffID": 121, "StatID": 2, "Val": 76 }, { "Max": 100, "StaffID": 121, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 121, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 121, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 121, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 121, "StatID": 7, "Val": 82 }, { "Max": 100, "StaffID": 121, "StatID": 8, "Val": 76 }, { "Max": 100, "StaffID": 121, "StatID": 9, "Val": 79 }, { "Max": 100, "StaffID": 121, "StatID": 10, "Val": 74 }, { "Max": 100, "StaffID": 289, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 289, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 289, "StatID": 4, "Val": 77 }, { "Max": 100, "StaffID": 289, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 289, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 289, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 289, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 289, "StatID": 9, "Val": 73 }, { "Max": 100, "StaffID": 289, "StatID": 10, "Val": 77 }, { "Max": 100, "StaffID": 82, "StatID": 2, "Val": 73 }, { "Max": 100, "StaffID": 82, "StatID": 3, "Val": 77 }, { "Max": 100, "StaffID": 82, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 82, "StatID": 5, "Val": 76 }, { "Max": 100, "StaffID": 82, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 82, "StatID": 7, "Val": 72 }, { "Max": 100, "StaffID": 82, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 82, "StatID": 9, "Val": 72 }, { "StaffID": 82, "StatID": 10, "Val": 78, "Max": 100 }, { "StaffID": 604, "StatID": 2, "Val": 79, "Max": 100 }, { "Max": 100, "StaffID": 604, "StatID": 3, "Val": 78 }, { "StaffID": 604, "StatID": 4, "Val": 69, "Max": 100 }, { "Max": 100, "StaffID": 604, "StatID": 5, "Val": 69 }, { "StaffID": 604, "StatID": 6, "Val": 73, "Max": 100 }, { "Max": 100, "StaffID": 604, "StatID": 7, "Val": 78 }, { "Max": 100, "StaffID": 604, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 604, "StatID": 9, "Val": 79 }, { "StaffID": 604, "StatID": 10, "Val": 69, "Max": 100 }, { "Max": 100, "StaffID": 127, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 127, "StatID": 3, "Val": 79 }, { "Max": 100, "StaffID": 127, "StatID": 4, "Val": 72 }, { "Max": 100, "StaffID": 127, "StatID": 5, "Val": 68 }, { "Max": 100, "StaffID": 127, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 127, "StatID": 7, "Val": 73 }, { "Max": 100, "StaffID": 127, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 127, "StatID": 9, "Val": 74 }, { "Max": 100, "StaffID": 127, "StatID": 10, "Val": 77 }, { "Max": 100, "StaffID": 130, "StatID": 2, "Val": 77 }, { "Max": 100, "StaffID": 130, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 130, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 130, "StatID": 5, "Val": 68 }, { "Max": 100, "StaffID": 130, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 130, "StatID": 7, "Val": 79 }, { "Max": 100, "StaffID": 130, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 130, "StatID": 9, "Val": 76 }, { "Max": 100, "StaffID": 130, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 264, "StatID": 2, "Val": 70 }, { "Max": 100, "StaffID": 264, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 264, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 264, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 264, "StatID": 6, "Val": 81 }, { "Max": 100, "StaffID": 264, "StatID": 7, "Val": 67 }, { "Max": 100, "StaffID": 264, "StatID": 8, "Val": 69 }, { "Max": 100, "StaffID": 264, "StatID": 9, "Val": 71 }, { "StaffID": 264, "StatID": 10, "Val": 78, "Max": 100 }, { "Max": 100, "StaffID": 399, "StatID": 2, "Val": 80 }, { "Max": 100, "StaffID": 399, "StatID": 3, "Val": 78 }, { "StaffID": 399, "StatID": 4, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 399, "StatID": 5, "Val": 66 }, { "Max": 100, "StaffID": 399, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 399, "StatID": 7, "Val": 77 }, { "Max": 100, "StaffID": 399, "StatID": 8, "Val": 70 }, { "Max": 100, "StaffID": 399, "StatID": 9, "Val": 79 }, { "StaffID": 399, "StatID": 10, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 140, "StatID": 2, "Val": 71 }, { "StaffID": 140, "StatID": 3, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 140, "StatID": 4, "Val": 72 }, { "Max": 100, "StaffID": 140, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 140, "StatID": 6, "Val": 70 }, { "Max": 100, "StaffID": 140, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 140, "StatID": 8, "Val": 69 }, { "Max": 100, "StaffID": 140, "StatID": 9, "Val": 72 }, { "StaffID": 140, "StatID": 10, "Val": 78, "Max": 100 }, { "StaffID": 242, "StatID": 2, "Val": 76, "Max": 100 }, { "Max": 100, "StaffID": 242, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 242, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 242, "StatID": 5, "Val": 69 }, { "Max": 100, "StaffID": 242, "StatID": 6, "Val": 69 }, { "StaffID": 242, "StatID": 7, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 242, "StatID": 8, "Val": 73 }, { "Max": 100, "StaffID": 242, "StatID": 9, "Val": 75 }, { "Max": 100, "StaffID": 242, "StatID": 10, "Val": 71 }, { "Max": 100, "StaffID": 99, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 99, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 99, "StatID": 4, "Val": 75 }, { "Max": 100, "StaffID": 99, "StatID": 5, "Val": 80 }, { "Max": 100, "StaffID": 99, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 99, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 99, "StatID": 8, "Val": 72 }, { "Max": 100, "StaffID": 99, "StatID": 9, "Val": 70 }, { "Max": 100, "StaffID": 99, "StatID": 10, "Val": 75 }, { "Max": 100, "StaffID": 322, "StatID": 2, "Val": 76 }, { "Max": 100, "StaffID": 322, "StatID": 3, "Val": 77 }, { "Max": 100, "StaffID": 322, "StatID": 4, "Val": 70 }, { "Max": 100, "StaffID": 322, "StatID": 5, "Val": 67 }, { "Max": 100, "StaffID": 322, "StatID": 6, "Val": 70 }, { "Max": 100, "StaffID": 322, "StatID": 7, "Val": 79 }, { "Max": 100, "StaffID": 322, "StatID": 8, "Val": 71 }, { "Max": 100, "StaffID": 322, "StatID": 9, "Val": 77 }, { "Max": 100, "StaffID": 322, "StatID": 10, "Val": 69 }, { "Max": 100, "StaffID": 117, "StatID": 2, "Val": 70 }, { "Max": 100, "StaffID": 117, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 117, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 117, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 117, "StatID": 6, "Val": 73 }, { "Max": 100, "StaffID": 117, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 117, "StatID": 8, "Val": 70 }, { "Max": 100, "StaffID": 117, "StatID": 9, "Val": 71 }, { "Max": 100, "StaffID": 117, "StatID": 10, "Val": 75 }, { "Max": 100, "StaffID": 245, "StatID": 2, "Val": 75 }, { "Max": 100, "StaffID": 245, "StatID": 3, "Val": 74 }, { "StaffID": 245, "StatID": 4, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 245, "StatID": 5, "Val": 66 }, { "Max": 100, "StaffID": 245, "StatID": 6, "Val": 70 }, { "StaffID": 245, "StatID": 7, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 245, "StatID": 8, "Val": 74 }, { "Max": 100, "StaffID": 245, "StatID": 9, "Val": 75 }, { "StaffID": 245, "StatID": 10, "Val": 69, "Max": 100 }, { "StaffID": 282, "StatID": 2, "Val": 76, "Max": 100 }, { "Max": 100, "StaffID": 282, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 282, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 282, "StatID": 5, "Val": 72 }, { "Max": 100, "StaffID": 282, "StatID": 6, "Val": 71 }, { "Max": 100, "StaffID": 282, "StatID": 7, "Val": 71 }, { "Max": 100, "StaffID": 282, "StatID": 8, "Val": 71 }, { "Max": 100, "StaffID": 282, "StatID": 9, "Val": 72 }, { "Max": 100, "StaffID": 282, "StatID": 10, "Val": 73 }, { "Max": 100, "StaffID": 88, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 88, "StatID": 3, "Val": 78 }, { "Max": 100, "StaffID": 88, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 88, "StatID": 5, "Val": 79 }, { "Max": 100, "StaffID": 88, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 88, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 88, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 88, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 88, "StatID": 10, "Val": 76 }, { "Max": 100, "StaffID": 263, "StatID": 2, "Val": 70 }, { "Max": 100, "StaffID": 263, "StatID": 3, "Val": 76 }, { "Max": 100, "StaffID": 263, "StatID": 4, "Val": 74 }, { "Max": 100, "StaffID": 263, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 263, "StatID": 6, "Val": 73 }, { "Max": 100, "StaffID": 263, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 263, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 263, "StatID": 9, "Val": 69 }, { "Max": 100, "StaffID": 263, "StatID": 10, "Val": 74 }, { "StaffID": 286, "StatID": 2, "Val": 80, "Max": 100 }, { "Max": 100, "StaffID": 286, "StatID": 3, "Val": 75 }, { "StaffID": 286, "StatID": 4, "Val": 67, "Max": 100 }, { "StaffID": 286, "StatID": 5, "Val": 66, "Max": 100 }, { "StaffID": 286, "StatID": 6, "Val": 70, "Max": 100 }, { "StaffID": 286, "StatID": 7, "Val": 82, "Max": 100 }, { "Max": 100, "StaffID": 286, "StatID": 8, "Val": 70 }, { "StaffID": 286, "StatID": 9, "Val": 76, "Max": 100 }, { "StaffID": 286, "StatID": 10, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 80, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 80, "StatID": 3, "Val": 74 }, { "Max": 100, "StaffID": 80, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 80, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 80, "StatID": 6, "Val": 73 }, { "Max": 100, "StaffID": 80, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 80, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 80, "StatID": 9, "Val": 69 }, { "Max": 100, "StaffID": 80, "StatID": 10, "Val": 74 }, { "StaffID": 603, "StatID": 2, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 603, "StatID": 3, "Val": 73 }, { "Max": 100, "StaffID": 603, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 603, "StatID": 5, "Val": 78 }, { "Max": 100, "StaffID": 603, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 603, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 603, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 603, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 603, "StatID": 10, "Val": 74 }, { "Max": 100, "StaffID": 280, "StatID": 2, "Val": 79 }, { "Max": 100, "StaffID": 280, "StatID": 3, "Val": 75 }, { "Max": 100, "StaffID": 280, "StatID": 4, "Val": 66 }, { "Max": 100, "StaffID": 280, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 280, "StatID": 6, "Val": 69 }, { "Max": 100, "StaffID": 280, "StatID": 7, "Val": 80 }, { "Max": 100, "StaffID": 280, "StatID": 8, "Val": 69 }, { "Max": 100, "StaffID": 280, "StatID": 9, "Val": 74 }, { "StaffID": 280, "StatID": 10, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 378, "StatID": 2, "Val": 71 }, { "Max": 100, "StaffID": 378, "StatID": 3, "Val": 71 }, { "Max": 100, "StaffID": 378, "StatID": 4, "Val": 71 }, { "Max": 100, "StaffID": 378, "StatID": 5, "Val": 73 }, { "Max": 100, "StaffID": 378, "StatID": 6, "Val": 71 }, { "StaffID": 378, "StatID": 7, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 378, "StatID": 8, "Val": 71 }, { "StaffID": 378, "StatID": 9, "Val": 72, "Max": 100 }, { "StaffID": 378, "StatID": 10, "Val": 72, "Max": 100 }, { "StaffID": 649, "StatID": 2, "Val": 78, "Max": 100 }, { "Max": 100, "StaffID": 649, "StatID": 3, "Val": 74 }, { "StaffID": 649, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 649, "StatID": 5, "Val": 63, "Max": 100 }, { "StaffID": 649, "StatID": 6, "Val": 67, "Max": 100 }, { "StaffID": 649, "StatID": 7, "Val": 75, "Max": 100 }, { "Max": 100, "StaffID": 649, "StatID": 8, "Val": 68 }, { "StaffID": 649, "StatID": 9, "Val": 75, "Max": 100 }, { "StaffID": 649, "StatID": 10, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 132, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 132, "StatID": 3, "Val": 72 }, { "Max": 100, "StaffID": 132, "StatID": 4, "Val": 73 }, { "Max": 100, "StaffID": 132, "StatID": 5, "Val": 77 }, { "Max": 100, "StaffID": 132, "StatID": 6, "Val": 75 }, { "Max": 100, "StaffID": 132, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 132, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 132, "StatID": 9, "Val": 67 }, { "Max": 100, "StaffID": 132, "StatID": 10, "Val": 73 }, { "StaffID": 379, "StatID": 2, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 379, "StatID": 3, "Val": 72 }, { "StaffID": 379, "StatID": 4, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 379, "StatID": 5, "Val": 67 }, { "Max": 100, "StaffID": 379, "StatID": 6, "Val": 70 }, { "StaffID": 379, "StatID": 7, "Val": 75, "Max": 100 }, { "Max": 100, "StaffID": 379, "StatID": 8, "Val": 70 }, { "StaffID": 379, "StatID": 9, "Val": 72, "Max": 100 }, { "StaffID": 379, "StatID": 10, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 301, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 301, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 301, "StatID": 4, "Val": 70 }, { "Max": 100, "StaffID": 301, "StatID": 5, "Val": 75 }, { "Max": 100, "StaffID": 301, "StatID": 6, "Val": 74 }, { "Max": 100, "StaffID": 301, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 301, "StatID": 8, "Val": 71 }, { "Max": 100, "StaffID": 301, "StatID": 9, "Val": 69 }, { "Max": 100, "StaffID": 301, "StatID": 10, "Val": 71 }, { "StaffID": 283, "StatID": 2, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 283, "StatID": 3, "Val": 71 }, { "Max": 100, "StaffID": 283, "StatID": 4, "Val": 66 }, { "Max": 100, "StaffID": 283, "StatID": 5, "Val": 66 }, { "StaffID": 283, "StatID": 6, "Val": 69, "Max": 100 }, { "StaffID": 283, "StatID": 7, "Val": 75, "Max": 100 }, { "Max": 100, "StaffID": 283, "StatID": 8, "Val": 69 }, { "StaffID": 283, "StatID": 9, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 283, "StatID": 10, "Val": 66 }, { "Max": 100, "StaffID": 411, "StatID": 2, "Val": 74 }, { "StaffID": 411, "StatID": 3, "Val": 70, "Max": 100 }, { "StaffID": 411, "StatID": 4, "Val": 62, "Max": 100 }, { "StaffID": 411, "StatID": 5, "Val": 62, "Max": 100 }, { "StaffID": 411, "StatID": 6, "Val": 68, "Max": 100 }, { "StaffID": 411, "StatID": 7, "Val": 76, "Max": 100 }, { "Max": 100, "StaffID": 411, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 411, "StatID": 9, "Val": 75 }, { "StaffID": 411, "StatID": 10, "Val": 63, "Max": 100 }, { "Max": 100, "StaffID": 610, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 610, "StatID": 3, "Val": 70 }, { "StaffID": 610, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 610, "StatID": 5, "Val": 63, "Max": 100 }, { "StaffID": 610, "StatID": 6, "Val": 68, "Max": 100 }, { "StaffID": 610, "StatID": 7, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 610, "StatID": 8, "Val": 68 }, { "StaffID": 610, "StatID": 9, "Val": 72, "Max": 100 }, { "StaffID": 610, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 377, "StatID": 2, "Val": 65, "Max": 100 }, { "StaffID": 377, "StatID": 3, "Val": 69, "Max": 100 }, { "StaffID": 377, "StatID": 4, "Val": 71, "Max": 100 }, { "StaffID": 377, "StatID": 5, "Val": 75, "Max": 100 }, { "Max": 100, "StaffID": 377, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 377, "StatID": 7, "Val": 66 }, { "Max": 100, "StaffID": 377, "StatID": 8, "Val": 69 }, { "StaffID": 377, "StatID": 9, "Val": 68, "Max": 100 }, { "StaffID": 377, "StatID": 10, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 284, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 284, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 284, "StatID": 4, "Val": 68 }, { "Max": 100, "StaffID": 284, "StatID": 5, "Val": 67 }, { "Max": 100, "StaffID": 284, "StatID": 6, "Val": 70 }, { "Max": 100, "StaffID": 284, "StatID": 7, "Val": 71 }, { "Max": 100, "StaffID": 284, "StatID": 8, "Val": 71 }, { "Max": 100, "StaffID": 284, "StatID": 9, "Val": 70 }, { "Max": 100, "StaffID": 284, "StatID": 10, "Val": 68 }, { "Max": 100, "StaffID": 375, "StatID": 2, "Val": 74 }, { "Max": 100, "StaffID": 375, "StatID": 3, "Val": 70 }, { "StaffID": 375, "StatID": 4, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 375, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 375, "StatID": 6, "Val": 68 }, { "Max": 100, "StaffID": 375, "StatID": 7, "Val": 74 }, { "Max": 100, "StaffID": 375, "StatID": 8, "Val": 67 }, { "Max": 100, "StaffID": 375, "StatID": 9, "Val": 73 }, { "Max": 100, "StaffID": 375, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 252, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 252, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 252, "StatID": 4, "Val": 69 }, { "Max": 100, "StaffID": 252, "StatID": 5, "Val": 72 }, { "StaffID": 252, "StatID": 6, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 252, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 252, "StatID": 8, "Val": 68 }, { "Max": 100, "StaffID": 252, "StatID": 9, "Val": 67 }, { "StaffID": 252, "StatID": 10, "Val": 68, "Max": 100 }, { "StaffID": 288, "StatID": 2, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 288, "StatID": 3, "Val": 69 }, { "StaffID": 288, "StatID": 4, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 288, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 288, "StatID": 6, "Val": 69 }, { "StaffID": 288, "StatID": 7, "Val": 75, "Max": 100 }, { "Max": 100, "StaffID": 288, "StatID": 8, "Val": 68 }, { "StaffID": 288, "StatID": 9, "Val": 72, "Max": 100 }, { "StaffID": 288, "StatID": 10, "Val": 66, "Max": 100 }, { "StaffID": 439, "StatID": 2, "Val": 72, "Max": 100 }, { "StaffID": 439, "StatID": 3, "Val": 69, "Max": 100 }, { "Max": 100, "StaffID": 439, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 439, "StatID": 5, "Val": 63 }, { "StaffID": 439, "StatID": 6, "Val": 66, "Max": 100 }, { "StaffID": 439, "StatID": 7, "Val": 74, "Max": 100 }, { "Max": 100, "StaffID": 439, "StatID": 8, "Val": 68 }, { "StaffID": 439, "StatID": 9, "Val": 71, "Max": 100 }, { "StaffID": 439, "StatID": 10, "Val": 62, "Max": 100 }, { "StaffID": 650, "StatID": 2, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 650, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 650, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 650, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 650, "StatID": 6, "Val": 67 }, { "StaffID": 650, "StatID": 7, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 650, "StatID": 8, "Val": 67 }, { "StaffID": 650, "StatID": 9, "Val": 70, "Max": 100 }, { "StaffID": 650, "StatID": 10, "Val": 63, "Max": 100 }, { "Max": 100, "StaffID": 647, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 647, "StatID": 3, "Val": 67 }, { "StaffID": 647, "StatID": 4, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 647, "StatID": 5, "Val": 73 }, { "StaffID": 647, "StatID": 6, "Val": 74, "Max": 100 }, { "Max": 100, "StaffID": 647, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 647, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 647, "StatID": 9, "Val": 66 }, { "StaffID": 647, "StatID": 10, "Val": 68, "Max": 100 }, { "StaffID": 380, "StatID": 2, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 380, "StatID": 3, "Val": 69 }, { "Max": 100, "StaffID": 380, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 380, "StatID": 5, "Val": 64 }, { "StaffID": 380, "StatID": 6, "Val": 67, "Max": 100 }, { "StaffID": 380, "StatID": 7, "Val": 74, "Max": 100 }, { "Max": 100, "StaffID": 380, "StatID": 8, "Val": 67 }, { "StaffID": 380, "StatID": 9, "Val": 69, "Max": 100 }, { "Max": 100, "StaffID": 380, "StatID": 10, "Val": 65 }, { "StaffID": 614, "StatID": 2, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 614, "StatID": 3, "Val": 67 }, { "Max": 100, "StaffID": 614, "StatID": 4, "Val": 65 }, { "Max": 100, "StaffID": 614, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 614, "StatID": 6, "Val": 67 }, { "StaffID": 614, "StatID": 7, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 614, "StatID": 8, "Val": 67 }, { "StaffID": 614, "StatID": 9, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 614, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 384, "StatID": 2, "Val": 69 }, { "Max": 100, "StaffID": 384, "StatID": 3, "Val": 67 }, { "Max": 100, "StaffID": 384, "StatID": 4, "Val": 65 }, { "Max": 100, "StaffID": 384, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 384, "StatID": 6, "Val": 66 }, { "StaffID": 384, "StatID": 7, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 384, "StatID": 8, "Val": 66 }, { "StaffID": 384, "StatID": 9, "Val": 67, "Max": 100 }, { "StaffID": 384, "StatID": 10, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 651, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 651, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 651, "StatID": 4, "Val": 66 }, { "StaffID": 651, "StatID": 5, "Val": 65, "Max": 100 }, { "StaffID": 651, "StatID": 6, "Val": 66, "Max": 100 }, { "StaffID": 651, "StatID": 7, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 651, "StatID": 8, "Val": 66 }, { "StaffID": 651, "StatID": 9, "Val": 69, "Max": 100 }, { "StaffID": 651, "StatID": 10, "Val": 65, "Max": 100 }, { "StaffID": 615, "StatID": 2, "Val": 69, "Max": 100 }, { "StaffID": 615, "StatID": 3, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 615, "StatID": 4, "Val": 65 }, { "Max": 100, "StaffID": 615, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 615, "StatID": 6, "Val": 67 }, { "Max": 100, "StaffID": 615, "StatID": 7, "Val": 70 }, { "Max": 100, "StaffID": 615, "StatID": 8, "Val": 66 }, { "StaffID": 615, "StatID": 9, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 615, "StatID": 10, "Val": 65 }, { "Max": 100, "StaffID": 656, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 656, "StatID": 3, "Val": 67 }, { "Max": 100, "StaffID": 656, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 656, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 656, "StatID": 6, "Val": 66 }, { "StaffID": 656, "StatID": 7, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 656, "StatID": 8, "Val": 65 }, { "StaffID": 656, "StatID": 9, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 656, "StatID": 10, "Val": 65 }, { "Max": 100, "StaffID": 383, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 383, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 383, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 383, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 383, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 383, "StatID": 7, "Val": 71 }, { "Max": 100, "StaffID": 383, "StatID": 8, "Val": 66 }, { "Max": 100, "StaffID": 383, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 383, "StatID": 10, "Val": 64 }, { "StaffID": 394, "StatID": 2, "Val": 67, "Max": 100 }, { "StaffID": 394, "StatID": 3, "Val": 68, "Max": 100 }, { "StaffID": 394, "StatID": 4, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 394, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 394, "StatID": 6, "Val": 66 }, { "StaffID": 394, "StatID": 7, "Val": 71, "Max": 100 }, { "Max": 100, "StaffID": 394, "StatID": 8, "Val": 66 }, { "StaffID": 394, "StatID": 9, "Val": 68, "Max": 100 }, { "StaffID": 394, "StatID": 10, "Val": 66, "Max": 100 }, { "StaffID": 667, "StatID": 2, "Val": 66, "Max": 100 }, { "StaffID": 667, "StatID": 3, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 667, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 667, "StatID": 5, "Val": 68 }, { "Max": 100, "StaffID": 667, "StatID": 6, "Val": 76 }, { "Max": 100, "StaffID": 667, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 667, "StatID": 8, "Val": 66 }, { "StaffID": 667, "StatID": 9, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 667, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 618, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 618, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 618, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 618, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 618, "StatID": 6, "Val": 63 }, { "Max": 100, "StaffID": 618, "StatID": 7, "Val": 70 }, { "Max": 100, "StaffID": 618, "StatID": 8, "Val": 63 }, { "Max": 100, "StaffID": 618, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 618, "StatID": 10, "Val": 64 }, { "Max": 100, "StaffID": 382, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 382, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 382, "StatID": 4, "Val": 65 }, { "Max": 100, "StaffID": 382, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 382, "StatID": 6, "Val": 64 }, { "StaffID": 382, "StatID": 7, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 382, "StatID": 8, "Val": 63 }, { "StaffID": 382, "StatID": 9, "Val": 65, "Max": 100 }, { "StaffID": 382, "StatID": 10, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 653, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 653, "StatID": 3, "Val": 68 }, { "Max": 100, "StaffID": 653, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 653, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 653, "StatID": 6, "Val": 63 }, { "StaffID": 653, "StatID": 7, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 653, "StatID": 8, "Val": 63 }, { "StaffID": 653, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 653, "StatID": 10, "Val": 63, "Max": 100 }, { "StaffID": 654, "StatID": 2, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 654, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 654, "StatID": 4, "Val": 64 }, { "StaffID": 654, "StatID": 5, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 654, "StatID": 6, "Val": 63 }, { "StaffID": 654, "StatID": 7, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 654, "StatID": 8, "Val": 62 }, { "StaffID": 654, "StatID": 9, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 654, "StatID": 10, "Val": 62 }, { "StaffID": 381, "StatID": 2, "Val": 69, "Max": 100 }, { "StaffID": 381, "StatID": 3, "Val": 68, "Max": 100 }, { "StaffID": 381, "StatID": 4, "Val": 64, "Max": 100 }, { "StaffID": 381, "StatID": 5, "Val": 62, "Max": 100 }, { "StaffID": 381, "StatID": 6, "Val": 63, "Max": 100 }, { "StaffID": 381, "StatID": 7, "Val": 69, "Max": 100 }, { "StaffID": 381, "StatID": 8, "Val": 63, "Max": 100 }, { "StaffID": 381, "StatID": 9, "Val": 66, "Max": 100 }, { "StaffID": 381, "StatID": 10, "Val": 63, "Max": 100 }, { "StaffID": 617, "StatID": 2, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 617, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 617, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 617, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 617, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 617, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 617, "StatID": 8, "Val": 61 }, { "StaffID": 617, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 617, "StatID": 10, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 657, "StatID": 2, "Val": 70 }, { "Max": 100, "StaffID": 657, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 657, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 657, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 657, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 657, "StatID": 7, "Val": 68 }, { "Max": 100, "StaffID": 657, "StatID": 8, "Val": 63 }, { "Max": 100, "StaffID": 657, "StatID": 9, "Val": 66 }, { "Max": 100, "StaffID": 657, "StatID": 10, "Val": 60 }, { "Max": 100, "StaffID": 123, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 123, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 123, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 123, "StatID": 5, "Val": 66 }, { "Max": 100, "StaffID": 123, "StatID": 6, "Val": 64 }, { "Max": 100, "StaffID": 123, "StatID": 7, "Val": 60 }, { "Max": 100, "StaffID": 123, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 123, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 123, "StatID": 10, "Val": 65 }, { "StaffID": 253, "StatID": 2, "Val": 68, "Max": 100 }, { "Max": 100, "StaffID": 253, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 253, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 253, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 253, "StatID": 6, "Val": 63 }, { "Max": 100, "StaffID": 253, "StatID": 7, "Val": 60 }, { "Max": 100, "StaffID": 253, "StatID": 8, "Val": 62 }, { "StaffID": 253, "StatID": 9, "Val": 62, "Max": 100 }, { "StaffID": 253, "StatID": 10, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 611, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 611, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 611, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 611, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 611, "StatID": 6, "Val": 63 }, { "StaffID": 611, "StatID": 7, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 611, "StatID": 8, "Val": 62 }, { "StaffID": 611, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 611, "StatID": 10, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 648, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 648, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 648, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 648, "StatID": 5, "Val": 65 }, { "Max": 100, "StaffID": 648, "StatID": 6, "Val": 63 }, { "Max": 100, "StaffID": 648, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 648, "StatID": 8, "Val": 62 }, { "StaffID": 648, "StatID": 9, "Val": 62, "Max": 100 }, { "StaffID": 648, "StatID": 10, "Val": 63, "Max": 100 }, { "StaffID": 652, "StatID": 2, "Val": 67, "Max": 100 }, { "StaffID": 652, "StatID": 3, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 652, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 652, "StatID": 5, "Val": 64 }, { "Max": 100, "StaffID": 652, "StatID": 6, "Val": 65 }, { "Max": 100, "StaffID": 652, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 652, "StatID": 8, "Val": 63 }, { "StaffID": 652, "StatID": 9, "Val": 64, "Max": 100 }, { "StaffID": 652, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 602, "StatID": 2, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 602, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 602, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 602, "StatID": 5, "Val": 63 }, { "Max": 100, "StaffID": 602, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 602, "StatID": 7, "Val": 64 }, { "Max": 100, "StaffID": 602, "StatID": 8, "Val": 62 }, { "StaffID": 602, "StatID": 9, "Val": 62, "Max": 100 }, { "StaffID": 602, "StatID": 10, "Val": 62, "Max": 100 }, { "StaffID": 655, "StatID": 2, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 655, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 655, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 655, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 655, "StatID": 6, "Val": 63 }, { "StaffID": 655, "StatID": 7, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 655, "StatID": 8, "Val": 62 }, { "StaffID": 655, "StatID": 9, "Val": 63, "Max": 100 }, { "StaffID": 655, "StatID": 10, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 387, "StatID": 2, "Val": 65 }, { "Max": 100, "StaffID": 387, "StatID": 3, "Val": 66 }, { "Max": 100, "StaffID": 387, "StatID": 4, "Val": 63 }, { "Max": 100, "StaffID": 387, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 387, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 387, "StatID": 7, "Val": 65 }, { "Max": 100, "StaffID": 387, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 387, "StatID": 9, "Val": 63 }, { "Max": 100, "StaffID": 387, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 407, "StatID": 2, "Val": 68 }, { "Max": 100, "StaffID": 407, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 407, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 407, "StatID": 5, "Val": 57 }, { "Max": 100, "StaffID": 407, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 407, "StatID": 7, "Val": 69 }, { "Max": 100, "StaffID": 407, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 407, "StatID": 9, "Val": 68 }, { "Max": 100, "StaffID": 407, "StatID": 10, "Val": 59 }, { "StaffID": 660, "StatID": 2, "Val": 67, "Max": 100 }, { "Max": 100, "StaffID": 660, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 660, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 660, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 660, "StatID": 6, "Val": 60 }, { "StaffID": 660, "StatID": 7, "Val": 63, "Max": 100 }, { "Max": 100, "StaffID": 660, "StatID": 8, "Val": 61 }, { "StaffID": 660, "StatID": 9, "Val": 63, "Max": 100 }, { "StaffID": 660, "StatID": 10, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 661, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 661, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 661, "StatID": 4, "Val": 64 }, { "StaffID": 661, "StatID": 5, "Val": 63, "Max": 100 }, { "Max": 100, "StaffID": 661, "StatID": 6, "Val": 61 }, { "StaffID": 661, "StatID": 7, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 661, "StatID": 8, "Val": 61 }, { "StaffID": 661, "StatID": 9, "Val": 63, "Max": 100 }, { "StaffID": 661, "StatID": 10, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 389, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 389, "StatID": 3, "Val": 65 }, { "Max": 100, "StaffID": 389, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 389, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 389, "StatID": 6, "Val": 63 }, { "Max": 100, "StaffID": 389, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 389, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 389, "StatID": 9, "Val": 62 }, { "Max": 100, "StaffID": 389, "StatID": 10, "Val": 64 }, { "StaffID": 621, "StatID": 2, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 621, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 621, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 621, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 621, "StatID": 6, "Val": 60 }, { "StaffID": 621, "StatID": 7, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 621, "StatID": 8, "Val": 61 }, { "StaffID": 621, "StatID": 9, "Val": 62, "Max": 100 }, { "StaffID": 621, "StatID": 10, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 659, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 659, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 659, "StatID": 4, "Val": 64 }, { "Max": 100, "StaffID": 659, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 659, "StatID": 6, "Val": 61 }, { "Max": 100, "StaffID": 659, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 659, "StatID": 8, "Val": 61 }, { "StaffID": 659, "StatID": 9, "Val": 62, "Max": 100 }, { "StaffID": 659, "StatID": 10, "Val": 64, "Max": 100 }, { "StaffID": 616, "StatID": 2, "Val": 65, "Max": 100 }, { "Max": 100, "StaffID": 616, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 616, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 616, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 616, "StatID": 6, "Val": 60 }, { "StaffID": 616, "StatID": 7, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 616, "StatID": 8, "Val": 61 }, { "StaffID": 616, "StatID": 9, "Val": 61, "Max": 100 }, { "StaffID": 616, "StatID": 10, "Val": 60, "Max": 100 }, { "StaffID": 613, "StatID": 2, "Val": 65, "Max": 100 }, { "StaffID": 613, "StatID": 3, "Val": 66, "Max": 100 }, { "Max": 100, "StaffID": 613, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 613, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 613, "StatID": 6, "Val": 59 }, { "StaffID": 613, "StatID": 7, "Val": 62, "Max": 100 }, { "StaffID": 613, "StatID": 8, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 613, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 613, "StatID": 10, "Val": 63 }, { "Max": 100, "StaffID": 646, "StatID": 2, "Val": 63 }, { "Max": 100, "StaffID": 646, "StatID": 3, "Val": 64 }, { "Max": 100, "StaffID": 646, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 646, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 646, "StatID": 6, "Val": 61 }, { "Max": 100, "StaffID": 646, "StatID": 7, "Val": 60 }, { "Max": 100, "StaffID": 646, "StatID": 8, "Val": 60 }, { "Max": 100, "StaffID": 646, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 646, "StatID": 10, "Val": 62 }, { "StaffID": 662, "StatID": 2, "Val": 64, "Max": 100 }, { "Max": 100, "StaffID": 662, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 662, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 662, "StatID": 5, "Val": 59 }, { "Max": 100, "StaffID": 662, "StatID": 6, "Val": 61 }, { "StaffID": 662, "StatID": 7, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 662, "StatID": 8, "Val": 60 }, { "StaffID": 662, "StatID": 9, "Val": 61, "Max": 100 }, { "StaffID": 662, "StatID": 10, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 619, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 619, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 619, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 619, "StatID": 5, "Val": 59 }, { "Max": 100, "StaffID": 619, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 619, "StatID": 7, "Val": 59 }, { "Max": 100, "StaffID": 619, "StatID": 8, "Val": 60 }, { "Max": 100, "StaffID": 619, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 619, "StatID": 10, "Val": 62 }, { "Max": 100, "StaffID": 405, "StatID": 2, "Val": 64 }, { "Max": 100, "StaffID": 405, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 405, "StatID": 4, "Val": 54 }, { "Max": 100, "StaffID": 405, "StatID": 5, "Val": 54 }, { "Max": 100, "StaffID": 405, "StatID": 6, "Val": 54 }, { "Max": 100, "StaffID": 405, "StatID": 7, "Val": 62 }, { "Max": 100, "StaffID": 405, "StatID": 8, "Val": 54 }, { "StaffID": 405, "StatID": 9, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 405, "StatID": 10, "Val": 54 }, { "Max": 100, "StaffID": 620, "StatID": 2, "Val": 62 }, { "Max": 100, "StaffID": 620, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 620, "StatID": 4, "Val": 62 }, { "Max": 100, "StaffID": 620, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 620, "StatID": 6, "Val": 55 }, { "Max": 100, "StaffID": 620, "StatID": 7, "Val": 58 }, { "Max": 100, "StaffID": 620, "StatID": 8, "Val": 55 }, { "Max": 100, "StaffID": 620, "StatID": 9, "Val": 61 }, { "Max": 100, "StaffID": 620, "StatID": 10, "Val": 62 }, { "Max": 100, "StaffID": 605, "StatID": 2, "Val": 54 }, { "Max": 100, "StaffID": 605, "StatID": 3, "Val": 57 }, { "StaffID": 605, "StatID": 4, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 605, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 605, "StatID": 6, "Val": 61 }, { "Max": 100, "StaffID": 605, "StatID": 7, "Val": 54 }, { "Max": 100, "StaffID": 605, "StatID": 8, "Val": 54 }, { "Max": 100, "StaffID": 605, "StatID": 9, "Val": 54 }, { "StaffID": 605, "StatID": 10, "Val": 63, "Max": 100 }, { "Max": 100, "StaffID": 612, "StatID": 2, "Val": 62 }, { "Max": 100, "StaffID": 612, "StatID": 3, "Val": 62 }, { "Max": 100, "StaffID": 612, "StatID": 4, "Val": 60 }, { "Max": 100, "StaffID": 612, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 612, "StatID": 6, "Val": 55 }, { "StaffID": 612, "StatID": 7, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 612, "StatID": 8, "Val": 59 }, { "StaffID": 612, "StatID": 9, "Val": 61, "Max": 100 }, { "StaffID": 612, "StatID": 10, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 601, "StatID": 2, "Val": 61 }, { "Max": 100, "StaffID": 601, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 601, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 601, "StatID": 5, "Val": 61 }, { "Max": 100, "StaffID": 601, "StatID": 6, "Val": 54 }, { "Max": 100, "StaffID": 601, "StatID": 7, "Val": 57 }, { "Max": 100, "StaffID": 601, "StatID": 8, "Val": 54 }, { "Max": 100, "StaffID": 601, "StatID": 9, "Val": 60 }, { "Max": 100, "StaffID": 601, "StatID": 10, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 2, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 3, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 4, "Val": 59 }, { "Max": 100, "StaffID": 385, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 385, "StatID": 6, "Val": 54 }, { "Max": 100, "StaffID": 385, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 385, "StatID": 8, "Val": 54 }, { "StaffID": 385, "StatID": 9, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 385, "StatID": 10, "Val": 59 }, { "StaffID": 658, "StatID": 2, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 658, "StatID": 3, "Val": 60 }, { "Max": 100, "StaffID": 658, "StatID": 4, "Val": 60 }, { "Max": 100, "StaffID": 658, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 658, "StatID": 6, "Val": 53 }, { "StaffID": 658, "StatID": 7, "Val": 58, "Max": 100 }, { "Max": 100, "StaffID": 658, "StatID": 8, "Val": 56 }, { "StaffID": 658, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 658, "StatID": 10, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 418, "StatID": 2, "Val": 54 }, { "Max": 100, "StaffID": 418, "StatID": 3, "Val": 54 }, { "StaffID": 418, "StatID": 4, "Val": 60, "Max": 100 }, { "Max": 100, "StaffID": 418, "StatID": 5, "Val": 60 }, { "Max": 100, "StaffID": 418, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 418, "StatID": 7, "Val": 54 }, { "Max": 100, "StaffID": 418, "StatID": 8, "Val": 55 }, { "StaffID": 418, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 418, "StatID": 10, "Val": 61, "Max": 100 }, { "StaffID": 402, "StatID": 2, "Val": 63, "Max": 100 }, { "StaffID": 402, "StatID": 3, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 402, "StatID": 4, "Val": 54 }, { "Max": 100, "StaffID": 402, "StatID": 5, "Val": 54 }, { "Max": 100, "StaffID": 402, "StatID": 6, "Val": 54 }, { "StaffID": 402, "StatID": 7, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 402, "StatID": 8, "Val": 54 }, { "StaffID": 402, "StatID": 9, "Val": 57, "Max": 100 }, { "Max": 100, "StaffID": 402, "StatID": 10, "Val": 54 }, { "StaffID": 406, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 406, "StatID": 3, "Val": 62, "Max": 100 }, { "Max": 100, "StaffID": 406, "StatID": 4, "Val": 58 }, { "Max": 100, "StaffID": 406, "StatID": 5, "Val": 59 }, { "Max": 100, "StaffID": 406, "StatID": 6, "Val": 56 }, { "Max": 100, "StaffID": 406, "StatID": 7, "Val": 58 }, { "Max": 100, "StaffID": 406, "StatID": 8, "Val": 58 }, { "StaffID": 406, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 406, "StatID": 10, "Val": 57, "Max": 100 }, { "StaffID": 664, "StatID": 2, "Val": 59, "Max": 100 }, { "StaffID": 664, "StatID": 3, "Val": 57, "Max": 100 }, { "Max": 100, "StaffID": 664, "StatID": 4, "Val": 58 }, { "Max": 100, "StaffID": 664, "StatID": 5, "Val": 58 }, { "Max": 100, "StaffID": 664, "StatID": 6, "Val": 56 }, { "StaffID": 664, "StatID": 7, "Val": 57, "Max": 100 }, { "Max": 100, "StaffID": 664, "StatID": 8, "Val": 58 }, { "StaffID": 664, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 664, "StatID": 10, "Val": 55, "Max": 100 }, { "Max": 100, "StaffID": 663, "StatID": 2, "Val": 61 }, { "Max": 100, "StaffID": 663, "StatID": 3, "Val": 59 }, { "Max": 100, "StaffID": 663, "StatID": 4, "Val": 59 }, { "StaffID": 663, "StatID": 5, "Val": 51, "Max": 100 }, { "Max": 100, "StaffID": 663, "StatID": 6, "Val": 52 }, { "Max": 100, "StaffID": 663, "StatID": 7, "Val": 52 }, { "Max": 100, "StaffID": 663, "StatID": 8, "Val": 52 }, { "StaffID": 663, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 663, "StatID": 10, "Val": 59, "Max": 100 }, { "Max": 100, "StaffID": 600, "StatID": 2, "Val": 52 }, { "Max": 100, "StaffID": 600, "StatID": 3, "Val": 53 }, { "StaffID": 600, "StatID": 4, "Val": 58, "Max": 100 }, { "StaffID": 600, "StatID": 5, "Val": 61, "Max": 100 }, { "Max": 100, "StaffID": 600, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 600, "StatID": 7, "Val": 52 }, { "Max": 100, "StaffID": 600, "StatID": 8, "Val": 52 }, { "StaffID": 600, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 600, "StatID": 10, "Val": 57, "Max": 100 }, { "StaffID": 674, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 674, "StatID": 3, "Val": 57, "Max": 100 }, { "Max": 100, "StaffID": 674, "StatID": 4, "Val": 52 }, { "Max": 100, "StaffID": 674, "StatID": 5, "Val": 52 }, { "Max": 100, "StaffID": 674, "StatID": 6, "Val": 57 }, { "Max": 100, "StaffID": 674, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 674, "StatID": 8, "Val": 58 }, { "Max": 100, "StaffID": 674, "StatID": 9, "Val": 58 }, { "Max": 100, "StaffID": 674, "StatID": 10, "Val": 52 }, { "StaffID": 606, "StatID": 2, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 3, "Val": 56, "Max": 100 }, { "Max": 100, "StaffID": 606, "StatID": 4, "Val": 56 }, { "Max": 100, "StaffID": 606, "StatID": 5, "Val": 56 }, { "StaffID": 606, "StatID": 6, "Val": 50, "Max": 100 }, { "StaffID": 606, "StatID": 7, "Val": 52, "Max": 100 }, { "StaffID": 606, "StatID": 8, "Val": 50, "Max": 100 }, { "StaffID": 606, "StatID": 9, "Val": 56, "Max": 100 }, { "StaffID": 606, "StatID": 10, "Val": 55, "Max": 100 }, { "StaffID": 671, "StatID": 2, "Val": 58, "Max": 100 }, { "StaffID": 671, "StatID": 3, "Val": 56, "Max": 100 }, { "StaffID": 671, "StatID": 4, "Val": 52, "Max": 100 }, { "StaffID": 671, "StatID": 5, "Val": 53, "Max": 100 }, { "Max": 100, "StaffID": 671, "StatID": 6, "Val": 48 }, { "StaffID": 671, "StatID": 7, "Val": 57, "Max": 100 }, { "Max": 100, "StaffID": 671, "StatID": 8, "Val": 49 }, { "StaffID": 671, "StatID": 9, "Val": 58, "Max": 100 }, { "StaffID": 671, "StatID": 10, "Val": 51, "Max": 100 }, { "StaffID": 409, "StatID": 2, "Val": 53, "Max": 100 }, { "StaffID": 409, "StatID": 3, "Val": 53, "Max": 100 }, { "StaffID": 409, "StatID": 4, "Val": 52, "Max": 100 }, { "StaffID": 409, "StatID": 5, "Val": 52, "Max": 100 }, { "Max": 100, "StaffID": 409, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 409, "StatID": 7, "Val": 48 }, { "Max": 100, "StaffID": 409, "StatID": 8, "Val": 48 }, { "StaffID": 409, "StatID": 9, "Val": 52, "Max": 100 }, { "StaffID": 409, "StatID": 10, "Val": 52, "Max": 100 }, { "Max": 100, "StaffID": 609, "StatID": 2, "Val": 52 }, { "Max": 100, "StaffID": 609, "StatID": 3, "Val": 55 }, { "StaffID": 609, "StatID": 4, "Val": 53, "Max": 100 }, { "Max": 100, "StaffID": 609, "StatID": 5, "Val": 54 }, { "Max": 100, "StaffID": 609, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 609, "StatID": 7, "Val": 53 }, { "Max": 100, "StaffID": 609, "StatID": 8, "Val": 48 }, { "Max": 100, "StaffID": 609, "StatID": 9, "Val": 52 }, { "StaffID": 609, "StatID": 10, "Val": 53, "Max": 100 }, { "StaffID": 672, "StatID": 2, "Val": 57, "Max": 100 }, { "StaffID": 672, "StatID": 3, "Val": 55, "Max": 100 }, { "StaffID": 672, "StatID": 4, "Val": 55, "Max": 100 }, { "StaffID": 672, "StatID": 5, "Val": 54, "Max": 100 }, { "StaffID": 672, "StatID": 6, "Val": 49, "Max": 100 }, { "StaffID": 672, "StatID": 7, "Val": 54, "Max": 100 }, { "StaffID": 672, "StatID": 8, "Val": 49, "Max": 100 }, { "StaffID": 672, "StatID": 9, "Val": 55, "Max": 100 }, { "StaffID": 672, "StatID": 10, "Val": 54, "Max": 100 }, { "Max": 100, "StaffID": 410, "StatID": 2, "Val": 54 }, { "Max": 100, "StaffID": 410, "StatID": 3, "Val": 52 }, { "Max": 100, "StaffID": 410, "StatID": 4, "Val": 51 }, { "Max": 100, "StaffID": 410, "StatID": 5, "Val": 52 }, { "Max": 100, "StaffID": 410, "StatID": 6, "Val": 52 }, { "Max": 100, "StaffID": 410, "StatID": 7, "Val": 54 }, { "Max": 100, "StaffID": 410, "StatID": 8, "Val": 52 }, { "Max": 100, "StaffID": 410, "StatID": 9, "Val": 53 }, { "Max": 100, "StaffID": 410, "StatID": 10, "Val": 51 }, { "StaffID": 673, "StatID": 2, "Val": 53, "Max": 100 }, { "StaffID": 673, "StatID": 3, "Val": 53, "Max": 100 }, { "StaffID": 673, "StatID": 4, "Val": 54, "Max": 100 }, { "StaffID": 673, "StatID": 5, "Val": 54, "Max": 100 }, { "Max": 100, "StaffID": 673, "StatID": 6, "Val": 48 }, { "StaffID": 673, "StatID": 7, "Val": 53, "Max": 100 }, { "Max": 100, "StaffID": 673, "StatID": 8, "Val": 48 }, { "StaffID": 673, "StatID": 9, "Val": 53, "Max": 100 }, { "StaffID": 673, "StatID": 10, "Val": 54, "Max": 100 }, { "Max": 100, "StaffID": 419, "StatID": 2, "Val": 52 }, { "Max": 100, "StaffID": 419, "StatID": 3, "Val": 52 }, { "Max": 100, "StaffID": 419, "StatID": 4, "Val": 53 }, { "Max": 100, "StaffID": 419, "StatID": 5, "Val": 52 }, { "Max": 100, "StaffID": 419, "StatID": 6, "Val": 48 }, { "StaffID": 419, "StatID": 7, "Val": 49, "Max": 100 }, { "Max": 100, "StaffID": 419, "StatID": 8, "Val": 48 }, { "Max": 100, "StaffID": 419, "StatID": 9, "Val": 52 }, { "Max": 100, "StaffID": 419, "StatID": 10, "Val": 53 }, { "StaffID": 665, "StatID": 2, "Val": 51, "Max": 100 }, { "StaffID": 665, "StatID": 3, "Val": 53, "Max": 100 }, { "Max": 100, "StaffID": 665, "StatID": 4, "Val": 52 }, { "Max": 100, "StaffID": 665, "StatID": 5, "Val": 52 }, { "Max": 100, "StaffID": 665, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 665, "StatID": 7, "Val": 48 }, { "Max": 100, "StaffID": 665, "StatID": 8, "Val": 48 }, { "Max": 100, "StaffID": 665, "StatID": 9, "Val": 52 }, { "Max": 100, "StaffID": 665, "StatID": 10, "Val": 52 }, { "StaffID": 668, "StatID": 2, "Val": 53, "Max": 100 }, { "StaffID": 668, "StatID": 3, "Val": 51, "Max": 100 }, { "Max": 100, "StaffID": 668, "StatID": 4, "Val": 50 }, { "Max": 100, "StaffID": 668, "StatID": 5, "Val": 51 }, { "Max": 100, "StaffID": 668, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 668, "StatID": 7, "Val": 50 }, { "Max": 100, "StaffID": 668, "StatID": 8, "Val": 50 }, { "StaffID": 668, "StatID": 9, "Val": 51, "Max": 100 }, { "Max": 100, "StaffID": 668, "StatID": 10, "Val": 50 }, { "Max": 100, "StaffID": 669, "StatID": 2, "Val": 52 }, { "Max": 100, "StaffID": 669, "StatID": 3, "Val": 49 }, { "Max": 100, "StaffID": 669, "StatID": 4, "Val": 49 }, { "Max": 100, "StaffID": 669, "StatID": 5, "Val": 51 }, { "Max": 100, "StaffID": 669, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 669, "StatID": 7, "Val": 50 }, { "Max": 100, "StaffID": 669, "StatID": 8, "Val": 50 }, { "Max": 100, "StaffID": 669, "StatID": 9, "Val": 50 }, { "Max": 100, "StaffID": 669, "StatID": 10, "Val": 50 }, { "StaffID": 607, "StatID": 2, "Val": 50, "Max": 100 }, { "Max": 100, "StaffID": 607, "StatID": 3, "Val": 52 }, { "Max": 100, "StaffID": 607, "StatID": 4, "Val": 50 }, { "Max": 100, "StaffID": 607, "StatID": 5, "Val": 51 }, { "Max": 100, "StaffID": 607, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 607, "StatID": 7, "Val": 50 }, { "Max": 100, "StaffID": 607, "StatID": 8, "Val": 48 }, { "Max": 100, "StaffID": 607, "StatID": 9, "Val": 49 }, { "Max": 100, "StaffID": 607, "StatID": 10, "Val": 50 }, { "StaffID": 670, "StatID": 2, "Val": 54, "Max": 100 }, { "StaffID": 670, "StatID": 3, "Val": 52, "Max": 100 }, { "Max": 100, "StaffID": 670, "StatID": 4, "Val": 50 }, { "Max": 100, "StaffID": 670, "StatID": 5, "Val": 49 }, { "Max": 100, "StaffID": 670, "StatID": 6, "Val": 48 }, { "StaffID": 670, "StatID": 7, "Val": 50, "Max": 100 }, { "Max": 100, "StaffID": 670, "StatID": 8, "Val": 48 }, { "StaffID": 670, "StatID": 9, "Val": 52, "Max": 100 }, { "StaffID": 670, "StatID": 10, "Val": 49, "Max": 100 }, { "StaffID": 666, "StatID": 2, "Val": 52, "Max": 100 }, { "StaffID": 666, "StatID": 3, "Val": 50, "Max": 100 }, { "Max": 100, "StaffID": 666, "StatID": 4, "Val": 49 }, { "Max": 100, "StaffID": 666, "StatID": 5, "Val": 50 }, { "Max": 100, "StaffID": 666, "StatID": 6, "Val": 48 }, { "Max": 100, "StaffID": 666, "StatID": 7, "Val": 49 }, { "Max": 100, "StaffID": 666, "StatID": 8, "Val": 48 }, { "StaffID": 666, "StatID": 9, "Val": 50, "Max": 100 }, { "Max": 100, "StaffID": 666, "StatID": 10, "Val": 49 }, { "Max": 100, "StaffID": 304, "StatID": 2, "Val": 67 }, { "Max": 100, "StaffID": 304, "StatID": 3, "Val": 67 }, { "Max": 100, "StaffID": 304, "StatID": 4, "Val": 61 }, { "Max": 100, "StaffID": 304, "StatID": 5, "Val": 62 }, { "Max": 100, "StaffID": 304, "StatID": 6, "Val": 62 }, { "Max": 100, "StaffID": 304, "StatID": 7, "Val": 63 }, { "Max": 100, "StaffID": 304, "StatID": 8, "Val": 61 }, { "Max": 100, "StaffID": 304, "StatID": 9, "Val": 62 }, { "Max": 100, "StaffID": 304, "StatID": 10, "Val": 62 }, { "StaffID": 305, "StatID": 2, "Val": 77, "Max": 100 }, { "StaffID": 305, "StatID": 3, "Val": 76, "Max": 100 }, { "StaffID": 305, "StatID": 4, "Val": 70, "Max": 100 }, { "Max": 100, "StaffID": 305, "StatID": 5, "Val": 70 }, { "StaffID": 305, "StatID": 6, "Val": 69, "Max": 100 }, { "StaffID": 305, "StatID": 7, "Val": 76, "Max": 100 }, { "Max": 100, "StaffID": 305, "StatID": 8, "Val": 72 }, { "StaffID": 305, "StatID": 9, "Val": 77, "Max": 100 }, { "StaffID": 305, "StatID": 10, "Val": 72, "Max": 100 }, { "Max": 100, "StaffID": 417, "StatID": 2, "Val": 66 }, { "Max": 100, "StaffID": 417, "StatID": 3, "Val": 63 }, { "Max": 100, "StaffID": 417, "StatID": 4, "Val": 58 }, { "Max": 100, "StaffID": 417, "StatID": 5, "Val": 57 }, { "Max": 100, "StaffID": 417, "StatID": 6, "Val": 60 }, { "Max": 100, "StaffID": 417, "StatID": 7, "Val": 61 }, { "Max": 100, "StaffID": 417, "StatID": 8, "Val": 56 }, { "Max": 100, "StaffID": 417, "StatID": 9, "Val": 62 }, { "Max": 100, "StaffID": 417, "StatID": 10, "Val": 58 }, { "Max": 100, "StaffID": 9, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 9, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 78, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 91, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 114, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 115, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 128, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 133, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 143, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 244, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 258, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 259, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 260, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 272, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 300, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 302, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 303, "StatID": 10, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 2, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 3, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 4, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 5, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 6, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 7, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 8, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 9, "Val": 0 }, { "Max": 100, "StaffID": 307, "StatID": 10, "Val": 0 }, { "StaffID": 563, "StatID": 13, "Val": 41, "Max": 100 }, { "StaffID": 563, "StatID": 25, "Val": 56, "Max": 100 }, { "StaffID": 564, "StatID": 19, "Val": 73, "Max": 100 }, { "StaffID": 564, "StatID": 20, "Val": 60, "Max": 100 }, { "StaffID": 564, "StatID": 26, "Val": 66, "Max": 100 }, { "StaffID": 564, "StatID": 27, "Val": 80, "Max": 100 }, { "StaffID": 564, "StatID": 28, "Val": 81, "Max": 100 }, { "StaffID": 564, "StatID": 29, "Val": 55, "Max": 100 }, { "StaffID": 564, "StatID": 30, "Val": 84, "Max": 100 }, { "StaffID": 564, "StatID": 31, "Val": 71, "Max": 100 }, { "StaffID": 565, "StatID": 13, "Val": 64, "Max": 100 }, { "StaffID": 565, "StatID": 25, "Val": 57, "Max": 100 }, { "StaffID": 565, "StatID": 43, "Val": 35, "Max": 100 }, { "StaffID": 566, "StatID": 11, "Val": 74, "Max": 100 }, { "StaffID": 566, "StatID": 22, "Val": 68, "Max": 100 }, { "StaffID": 566, "StatID": 23, "Val": 74, "Max": 100 }, { "StaffID": 566, "StatID": 24, "Val": 56, "Max": 100 } ], "Staff_Performancestats_StartOfMonth": [ { "StaffID": 4, "StatID": 0, "Val": 87 }, { "StaffID": 4, "StatID": 1, "Val": 89 }, { "StaffID": 4, "StatID": 14, "Val": 88 }, { "StaffID": 4, "StatID": 15, "Val": 87 }, { "StaffID": 4, "StatID": 16, "Val": 91 }, { "StaffID": 4, "StatID": 17, "Val": 88 }, { "StaffID": 6, "StatID": 13, "Val": 85 }, { "StaffID": 6, "StatID": 25, "Val": 85 }, { "StaffID": 7, "StatID": 13, "Val": 88 }, { "StaffID": 7, "StatID": 25, "Val": 89 }, { "StaffID": 13, "StatID": 2, "Val": 79 }, { "StaffID": 13, "StatID": 3, "Val": 79 }, { "StaffID": 13, "StatID": 4, "Val": 86 }, { "StaffID": 13, "StatID": 5, "Val": 86 }, { "StaffID": 13, "StatID": 6, "Val": 78 }, { "StaffID": 13, "StatID": 7, "Val": 81 }, { "StaffID": 13, "StatID": 8, "Val": 82 }, { "StaffID": 13, "StatID": 9, "Val": 81 }, { "StaffID": 13, "StatID": 10, "Val": 85 }, { "StaffID": 26, "StatID": 19, "Val": 92 }, { "StaffID": 26, "StatID": 20, "Val": 90 }, { "StaffID": 26, "StatID": 26, "Val": 90 }, { "StaffID": 26, "StatID": 27, "Val": 89 }, { "StaffID": 26, "StatID": 28, "Val": 91 }, { "StaffID": 26, "StatID": 29, "Val": 89 }, { "StaffID": 26, "StatID": 30, "Val": 88 }, { "StaffID": 26, "StatID": 31, "Val": 89 }, { "StaffID": 28, "StatID": 0, "Val": 92 }, { "StaffID": 28, "StatID": 1, "Val": 95 }, { "StaffID": 28, "StatID": 14, "Val": 94 }, { "StaffID": 28, "StatID": 15, "Val": 92 }, { "StaffID": 28, "StatID": 16, "Val": 95 }, { "StaffID": 28, "StatID": 17, "Val": 90 }, { "StaffID": 30, "StatID": 0, "Val": 87 }, { "StaffID": 30, "StatID": 1, "Val": 84 }, { "StaffID": 30, "StatID": 14, "Val": 84 }, { "StaffID": 30, "StatID": 15, "Val": 86 }, { "StaffID": 30, "StatID": 16, "Val": 85 }, { "StaffID": 30, "StatID": 17, "Val": 88 }, { "StaffID": 33, "StatID": 1, "Val": 86 }, { "StaffID": 33, "StatID": 14, "Val": 85 }, { "StaffID": 33, "StatID": 15, "Val": 83 }, { "StaffID": 33, "StatID": 16, "Val": 86 }, { "StaffID": 33, "StatID": 17, "Val": 83 }, { "StaffID": 37, "StatID": 19, "Val": 88 }, { "StaffID": 37, "StatID": 20, "Val": 95 }, { "StaffID": 37, "StatID": 26, "Val": 95 }, { "StaffID": 37, "StatID": 27, "Val": 94 }, { "StaffID": 37, "StatID": 28, "Val": 92 }, { "StaffID": 37, "StatID": 29, "Val": 95 }, { "StaffID": 37, "StatID": 30, "Val": 93 }, { "StaffID": 37, "StatID": 31, "Val": 90 }, { "StaffID": 38, "StatID": 19, "Val": 89 }, { "StaffID": 38, "StatID": 20, "Val": 88 }, { "StaffID": 38, "StatID": 26, "Val": 93 }, { "StaffID": 38, "StatID": 27, "Val": 92 }, { "StaffID": 38, "StatID": 28, "Val": 90 }, { "StaffID": 38, "StatID": 29, "Val": 90 }, { "StaffID": 38, "StatID": 30, "Val": 94 }, { "StaffID": 38, "StatID": 31, "Val": 93 }, { "StaffID": 39, "StatID": 19, "Val": 82 }, { "StaffID": 39, "StatID": 20, "Val": 81 }, { "StaffID": 39, "StatID": 26, "Val": 82 }, { "StaffID": 39, "StatID": 27, "Val": 83 }, { "StaffID": 39, "StatID": 28, "Val": 82 }, { "StaffID": 39, "StatID": 29, "Val": 80 }, { "StaffID": 39, "StatID": 30, "Val": 83 }, { "StaffID": 39, "StatID": 31, "Val": 82 }, { "StaffID": 40, "StatID": 20, "Val": 86 }, { "StaffID": 40, "StatID": 26, "Val": 87 }, { "StaffID": 40, "StatID": 27, "Val": 86 }, { "StaffID": 40, "StatID": 28, "Val": 85 }, { "StaffID": 40, "StatID": 29, "Val": 86 }, { "StaffID": 40, "StatID": 30, "Val": 85 }, { "StaffID": 40, "StatID": 31, "Val": 85 }, { "StaffID": 43, "StatID": 19, "Val": 85 }, { "StaffID": 43, "StatID": 20, "Val": 84 }, { "StaffID": 43, "StatID": 27, "Val": 86 }, { "StaffID": 43, "StatID": 28, "Val": 85 }, { "StaffID": 43, "StatID": 29, "Val": 83 }, { "StaffID": 43, "StatID": 30, "Val": 86 }, { "StaffID": 43, "StatID": 31, "Val": 85 }, { "StaffID": 44, "StatID": 19, "Val": 86 }, { "StaffID": 44, "StatID": 20, "Val": 85 }, { "StaffID": 44, "StatID": 26, "Val": 86 }, { "StaffID": 44, "StatID": 27, "Val": 87 }, { "StaffID": 44, "StatID": 28, "Val": 86 }, { "StaffID": 44, "StatID": 29, "Val": 84 }, { "StaffID": 44, "StatID": 30, "Val": 87 }, { "StaffID": 44, "StatID": 31, "Val": 86 }, { "StaffID": 47, "StatID": 13, "Val": 84 }, { "StaffID": 47, "StatID": 25, "Val": 86 }, { "StaffID": 48, "StatID": 13, "Val": 87 }, { "StaffID": 48, "StatID": 25, "Val": 89 }, { "StaffID": 49, "StatID": 13, "Val": 86 }, { "StaffID": 49, "StatID": 25, "Val": 87 }, { "StaffID": 50, "StatID": 13, "Val": 96 }, { "StaffID": 50, "StatID": 25, "Val": 92 }, { "StaffID": 51, "StatID": 13, "Val": 97 }, { "StaffID": 51, "StatID": 25, "Val": 91 }, { "StaffID": 54, "StatID": 13, "Val": 77 }, { "StaffID": 54, "StatID": 25, "Val": 83 }, { "StaffID": 55, "StatID": 13, "Val": 81 }, { "StaffID": 55, "StatID": 25, "Val": 82 }, { "StaffID": 56, "StatID": 13, "Val": 82 }, { "StaffID": 56, "StatID": 25, "Val": 83 }, { "StaffID": 58, "StatID": 25, "Val": 82 }, { "StaffID": 59, "StatID": 13, "Val": 76 }, { "StaffID": 59, "StatID": 25, "Val": 87 }, { "StaffID": 60, "StatID": 13, "Val": 83 }, { "StaffID": 60, "StatID": 25, "Val": 85 }, { "StaffID": 61, "StatID": 13, "Val": 84 }, { "StaffID": 61, "StatID": 25, "Val": 83 }, { "StaffID": 63, "StatID": 13, "Val": 82 }, { "StaffID": 63, "StatID": 25, "Val": 83 }, { "StaffID": 64, "StatID": 13, "Val": 82 }, { "StaffID": 64, "StatID": 25, "Val": 82 }, { "StaffID": 109, "StatID": 2, "Val": 63 }, { "StaffID": 109, "StatID": 3, "Val": 67 }, { "StaffID": 109, "StatID": 4, "Val": 68 }, { "StaffID": 109, "StatID": 5, "Val": 73 }, { "StaffID": 109, "StatID": 6, "Val": 72 }, { "StaffID": 109, "StatID": 7, "Val": 67 }, { "StaffID": 109, "StatID": 8, "Val": 65 }, { "StaffID": 109, "StatID": 9, "Val": 70 }, { "StaffID": 109, "StatID": 10, "Val": 69 }, { "StaffID": 110, "StatID": 2, "Val": 61 }, { "StaffID": 110, "StatID": 3, "Val": 62 }, { "StaffID": 110, "StatID": 4, "Val": 71 }, { "StaffID": 110, "StatID": 5, "Val": 74 }, { "StaffID": 110, "StatID": 6, "Val": 62 }, { "StaffID": 110, "StatID": 7, "Val": 67 }, { "StaffID": 110, "StatID": 8, "Val": 75 }, { "StaffID": 110, "StatID": 9, "Val": 62 }, { "StaffID": 110, "StatID": 10, "Val": 61 }, { "StaffID": 116, "StatID": 2, "Val": 77 }, { "StaffID": 116, "StatID": 3, "Val": 79 }, { "StaffID": 116, "StatID": 4, "Val": 79 }, { "StaffID": 116, "StatID": 5, "Val": 74 }, { "StaffID": 116, "StatID": 6, "Val": 80 }, { "StaffID": 116, "StatID": 7, "Val": 71 }, { "StaffID": 116, "StatID": 8, "Val": 76 }, { "StaffID": 116, "StatID": 9, "Val": 81 }, { "StaffID": 116, "StatID": 10, "Val": 81 }, { "StaffID": 119, "StatID": 3, "Val": 72 }, { "StaffID": 119, "StatID": 4, "Val": 81 }, { "StaffID": 119, "StatID": 5, "Val": 67 }, { "StaffID": 119, "StatID": 6, "Val": 66 }, { "StaffID": 119, "StatID": 7, "Val": 66 }, { "StaffID": 119, "StatID": 8, "Val": 66 }, { "StaffID": 119, "StatID": 9, "Val": 71 }, { "StaffID": 119, "StatID": 10, "Val": 75 }, { "StaffID": 146, "StatID": 25, "Val": 77 }, { "StaffID": 148, "StatID": 13, "Val": 75 }, { "StaffID": 148, "StatID": 25, "Val": 79 }, { "StaffID": 149, "StatID": 13, "Val": 78 }, { "StaffID": 149, "StatID": 25, "Val": 76 }, { "StaffID": 151, "StatID": 13, "Val": 78 }, { "StaffID": 151, "StatID": 25, "Val": 78 }, { "StaffID": 152, "StatID": 13, "Val": 76 }, { "StaffID": 152, "StatID": 25, "Val": 82 }, { "StaffID": 153, "StatID": 13, "Val": 76 }, { "StaffID": 155, "StatID": 13, "Val": 78 }, { "StaffID": 155, "StatID": 25, "Val": 77 }, { "StaffID": 158, "StatID": 13, "Val": 77 }, { "StaffID": 158, "StatID": 25, "Val": 73 }, { "StaffID": 159, "StatID": 25, "Val": 79 }, { "StaffID": 160, "StatID": 13, "Val": 77 }, { "StaffID": 160, "StatID": 25, "Val": 75 }, { "StaffID": 161, "StatID": 13, "Val": 75 }, { "StaffID": 161, "StatID": 25, "Val": 80 }, { "StaffID": 162, "StatID": 13, "Val": 73 }, { "StaffID": 162, "StatID": 25, "Val": 73 }, { "StaffID": 163, "StatID": 13, "Val": 73 }, { "StaffID": 163, "StatID": 25, "Val": 78 }, { "StaffID": 165, "StatID": 13, "Val": 70 }, { "StaffID": 165, "StatID": 25, "Val": 73 }, { "StaffID": 166, "StatID": 13, "Val": 73 }, { "StaffID": 166, "StatID": 25, "Val": 75 }, { "StaffID": 168, "StatID": 13, "Val": 82 }, { "StaffID": 168, "StatID": 25, "Val": 81 }, { "StaffID": 169, "StatID": 13, "Val": 79 }, { "StaffID": 169, "StatID": 25, "Val": 80 }, { "StaffID": 172, "StatID": 13, "Val": 75 }, { "StaffID": 172, "StatID": 25, "Val": 76 }, { "StaffID": 173, "StatID": 13, "Val": 74 }, { "StaffID": 173, "StatID": 25, "Val": 79 }, { "StaffID": 174, "StatID": 13, "Val": 76 }, { "StaffID": 174, "StatID": 25, "Val": 75 }, { "StaffID": 175, "StatID": 13, "Val": 77 }, { "StaffID": 175, "StatID": 25, "Val": 80 }, { "StaffID": 178, "StatID": 25, "Val": 77 }, { "StaffID": 179, "StatID": 13, "Val": 76 }, { "StaffID": 179, "StatID": 25, "Val": 76 }, { "StaffID": 181, "StatID": 25, "Val": 74 }, { "StaffID": 183, "StatID": 13, "Val": 74 }, { "StaffID": 183, "StatID": 25, "Val": 78 }, { "StaffID": 184, "StatID": 13, "Val": 74 }, { "StaffID": 184, "StatID": 25, "Val": 73 }, { "StaffID": 187, "StatID": 13, "Val": 73 }, { "StaffID": 187, "StatID": 25, "Val": 71 }, { "StaffID": 191, "StatID": 13, "Val": 73 }, { "StaffID": 191, "StatID": 25, "Val": 74 }, { "StaffID": 193, "StatID": 25, "Val": 71 }, { "StaffID": 195, "StatID": 13, "Val": 71 }, { "StaffID": 195, "StatID": 25, "Val": 74 }, { "StaffID": 247, "StatID": 2, "Val": 75 }, { "StaffID": 247, "StatID": 3, "Val": 66 }, { "StaffID": 247, "StatID": 4, "Val": 69 }, { "StaffID": 247, "StatID": 5, "Val": 68 }, { "StaffID": 247, "StatID": 6, "Val": 80 }, { "StaffID": 247, "StatID": 7, "Val": 64 }, { "StaffID": 247, "StatID": 8, "Val": 68 }, { "StaffID": 247, "StatID": 9, "Val": 69 }, { "StaffID": 247, "StatID": 10, "Val": 71 }, { "StaffID": 287, "StatID": 2, "Val": 47 }, { "StaffID": 287, "StatID": 3, "Val": 48 }, { "StaffID": 287, "StatID": 4, "Val": 55 }, { "StaffID": 287, "StatID": 5, "Val": 68 }, { "StaffID": 287, "StatID": 6, "Val": 70 }, { "StaffID": 287, "StatID": 7, "Val": 64 }, { "StaffID": 287, "StatID": 8, "Val": 75 }, { "StaffID": 287, "StatID": 9, "Val": 54 }, { "StaffID": 287, "StatID": 10, "Val": 60 }, { "StaffID": 292, "StatID": 11, "Val": 95 }, { "StaffID": 292, "StatID": 22, "Val": 94 }, { "StaffID": 292, "StatID": 23, "Val": 95 }, { "StaffID": 292, "StatID": 24, "Val": 93 }, { "StaffID": 293, "StatID": 11, "Val": 87 }, { "StaffID": 293, "StatID": 22, "Val": 90 }, { "StaffID": 293, "StatID": 23, "Val": 88 }, { "StaffID": 293, "StatID": 24, "Val": 90 }, { "StaffID": 299, "StatID": 11, "Val": 90 }, { "StaffID": 299, "StatID": 22, "Val": 86 }, { "StaffID": 299, "StatID": 23, "Val": 90 }, { "StaffID": 299, "StatID": 24, "Val": 95 }, { "StaffID": 306, "StatID": 2, "Val": 50 }, { "StaffID": 306, "StatID": 3, "Val": 50 }, { "StaffID": 306, "StatID": 4, "Val": 65 }, { "StaffID": 306, "StatID": 5, "Val": 63 }, { "StaffID": 306, "StatID": 6, "Val": 68 }, { "StaffID": 306, "StatID": 7, "Val": 57 }, { "StaffID": 306, "StatID": 8, "Val": 69 }, { "StaffID": 306, "StatID": 9, "Val": 54 }, { "StaffID": 306, "StatID": 10, "Val": 64 }, { "StaffID": 308, "StatID": 2, "Val": 45 }, { "StaffID": 308, "StatID": 3, "Val": 50 }, { "StaffID": 308, "StatID": 4, "Val": 54 }, { "StaffID": 308, "StatID": 5, "Val": 55 }, { "StaffID": 308, "StatID": 6, "Val": 44 }, { "StaffID": 308, "StatID": 7, "Val": 46 }, { "StaffID": 308, "StatID": 8, "Val": 57 }, { "StaffID": 308, "StatID": 9, "Val": 47 }, { "StaffID": 308, "StatID": 10, "Val": 68 }, { "StaffID": 309, "StatID": 11, "Val": 78 }, { "StaffID": 309, "StatID": 22, "Val": 80 }, { "StaffID": 309, "StatID": 23, "Val": 76 }, { "StaffID": 309, "StatID": 24, "Val": 78 }, { "StaffID": 310, "StatID": 11, "Val": 78 }, { "StaffID": 310, "StatID": 22, "Val": 82 }, { "StaffID": 310, "StatID": 23, "Val": 77 }, { "StaffID": 310, "StatID": 24, "Val": 80 }, { "StaffID": 311, "StatID": 11, "Val": 75 }, { "StaffID": 311, "StatID": 22, "Val": 76 }, { "StaffID": 311, "StatID": 23, "Val": 73 }, { "StaffID": 311, "StatID": 24, "Val": 75 }, { "StaffID": 312, "StatID": 11, "Val": 76 }, { "StaffID": 312, "StatID": 22, "Val": 76 }, { "StaffID": 312, "StatID": 24, "Val": 75 }, { "StaffID": 313, "StatID": 11, "Val": 74 }, { "StaffID": 313, "StatID": 22, "Val": 74 }, { "StaffID": 313, "StatID": 23, "Val": 72 }, { "StaffID": 314, "StatID": 11, "Val": 80 }, { "StaffID": 314, "StatID": 22, "Val": 82 }, { "StaffID": 314, "StatID": 23, "Val": 79 }, { "StaffID": 314, "StatID": 24, "Val": 81 }, { "StaffID": 315, "StatID": 11, "Val": 80 }, { "StaffID": 315, "StatID": 22, "Val": 82 }, { "StaffID": 315, "StatID": 23, "Val": 79 }, { "StaffID": 315, "StatID": 24, "Val": 81 }, { "StaffID": 316, "StatID": 11, "Val": 79 }, { "StaffID": 316, "StatID": 22, "Val": 81 }, { "StaffID": 316, "StatID": 23, "Val": 80 }, { "StaffID": 316, "StatID": 24, "Val": 82 }, { "StaffID": 317, "StatID": 11, "Val": 86 }, { "StaffID": 317, "StatID": 22, "Val": 88 }, { "StaffID": 317, "StatID": 23, "Val": 81 }, { "StaffID": 317, "StatID": 24, "Val": 83 }, { "StaffID": 318, "StatID": 11, "Val": 78 }, { "StaffID": 318, "StatID": 22, "Val": 80 }, { "StaffID": 318, "StatID": 23, "Val": 77 }, { "StaffID": 318, "StatID": 24, "Val": 79 }, { "StaffID": 319, "StatID": 11, "Val": 78 }, { "StaffID": 319, "StatID": 22, "Val": 80 }, { "StaffID": 319, "StatID": 23, "Val": 77 }, { "StaffID": 319, "StatID": 24, "Val": 79 }, { "StaffID": 320, "StatID": 13, "Val": 90 }, { "StaffID": 320, "StatID": 25, "Val": 87 }, { "StaffID": 321, "StatID": 13, "Val": 85 }, { "StaffID": 321, "StatID": 25, "Val": 86 }, { "StaffID": 6, "StatID": 43, "Val": 86 }, { "StaffID": 7, "StatID": 43, "Val": 90 }, { "StaffID": 47, "StatID": 43, "Val": 85 }, { "StaffID": 48, "StatID": 43, "Val": 83 }, { "StaffID": 49, "StatID": 43, "Val": 86 }, { "StaffID": 50, "StatID": 43, "Val": 91 }, { "StaffID": 51, "StatID": 43, "Val": 92 }, { "StaffID": 54, "StatID": 43, "Val": 87 }, { "StaffID": 55, "StatID": 43, "Val": 82 }, { "StaffID": 56, "StatID": 43, "Val": 84 }, { "StaffID": 58, "StatID": 43, "Val": 84 }, { "StaffID": 59, "StatID": 43, "Val": 84 }, { "StaffID": 60, "StatID": 43, "Val": 84 }, { "StaffID": 61, "StatID": 43, "Val": 83 }, { "StaffID": 63, "StatID": 43, "Val": 83 }, { "StaffID": 64, "StatID": 43, "Val": 83 }, { "StaffID": 146, "StatID": 43, "Val": 78 }, { "StaffID": 148, "StatID": 43, "Val": 78 }, { "StaffID": 149, "StatID": 43, "Val": 77 }, { "StaffID": 151, "StatID": 43, "Val": 79 }, { "StaffID": 152, "StatID": 43, "Val": 78 }, { "StaffID": 153, "StatID": 43, "Val": 77 }, { "StaffID": 155, "StatID": 43, "Val": 78 }, { "StaffID": 159, "StatID": 43, "Val": 80 }, { "StaffID": 160, "StatID": 43, "Val": 76 }, { "StaffID": 161, "StatID": 43, "Val": 79 }, { "StaffID": 162, "StatID": 43, "Val": 73 }, { "StaffID": 163, "StatID": 43, "Val": 78 }, { "StaffID": 165, "StatID": 43, "Val": 71 }, { "StaffID": 166, "StatID": 43, "Val": 73 }, { "StaffID": 168, "StatID": 43, "Val": 81 }, { "StaffID": 169, "StatID": 43, "Val": 78 }, { "StaffID": 172, "StatID": 43, "Val": 77 }, { "StaffID": 174, "StatID": 43, "Val": 76 }, { "StaffID": 175, "StatID": 43, "Val": 79 }, { "StaffID": 178, "StatID": 43, "Val": 78 }, { "StaffID": 179, "StatID": 43, "Val": 77 }, { "StaffID": 181, "StatID": 43, "Val": 76 }, { "StaffID": 183, "StatID": 43, "Val": 77 }, { "StaffID": 184, "StatID": 43, "Val": 74 }, { "StaffID": 187, "StatID": 43, "Val": 76 }, { "StaffID": 191, "StatID": 43, "Val": 76 }, { "StaffID": 193, "StatID": 43, "Val": 75 }, { "StaffID": 195, "StatID": 43, "Val": 72 }, { "StaffID": 320, "StatID": 43, "Val": 95 }, { "StaffID": 321, "StatID": 43, "Val": 85 }, { "StaffID": 333, "StatID": 0, "Val": 95 }, { "StaffID": 333, "StatID": 1, "Val": 93 }, { "StaffID": 333, "StatID": 14, "Val": 92 }, { "StaffID": 333, "StatID": 15, "Val": 95 }, { "StaffID": 333, "StatID": 16, "Val": 94 }, { "StaffID": 333, "StatID": 17, "Val": 93 }, { "StaffID": 334, "StatID": 0, "Val": 89 }, { "StaffID": 334, "StatID": 1, "Val": 88 }, { "StaffID": 334, "StatID": 14, "Val": 87 }, { "StaffID": 334, "StatID": 15, "Val": 89 }, { "StaffID": 334, "StatID": 16, "Val": 90 }, { "StaffID": 334, "StatID": 17, "Val": 90 }, { "StaffID": 335, "StatID": 0, "Val": 86 }, { "StaffID": 335, "StatID": 1, "Val": 88 }, { "StaffID": 335, "StatID": 14, "Val": 87 }, { "StaffID": 335, "StatID": 15, "Val": 85 }, { "StaffID": 335, "StatID": 16, "Val": 89 }, { "StaffID": 335, "StatID": 17, "Val": 83 }, { "StaffID": 337, "StatID": 19, "Val": 87 }, { "StaffID": 337, "StatID": 20, "Val": 89 }, { "StaffID": 337, "StatID": 26, "Val": 88 }, { "StaffID": 337, "StatID": 27, "Val": 87 }, { "StaffID": 337, "StatID": 28, "Val": 86 }, { "StaffID": 337, "StatID": 29, "Val": 87 }, { "StaffID": 337, "StatID": 30, "Val": 88 }, { "StaffID": 337, "StatID": 31, "Val": 87 }, { "StaffID": 341, "StatID": 25, "Val": 74 }, { "StaffID": 341, "StatID": 43, "Val": 75 }, { "StaffID": 343, "StatID": 13, "Val": 74 }, { "StaffID": 343, "StatID": 25, "Val": 78 }, { "StaffID": 343, "StatID": 43, "Val": 75 }, { "StaffID": 344, "StatID": 13, "Val": 74 }, { "StaffID": 344, "StatID": 25, "Val": 75 }, { "StaffID": 344, "StatID": 43, "Val": 76 }, { "StaffID": 345, "StatID": 13, "Val": 72 }, { "StaffID": 345, "StatID": 25, "Val": 70 }, { "StaffID": 345, "StatID": 43, "Val": 74 }, { "StaffID": 361, "StatID": 13, "Val": 73 }, { "StaffID": 361, "StatID": 43, "Val": 72 }, { "StaffID": 364, "StatID": 13, "Val": 72 }, { "StaffID": 364, "StatID": 25, "Val": 74 }, { "StaffID": 364, "StatID": 43, "Val": 70 }, { "StaffID": 365, "StatID": 0, "Val": 89 }, { "StaffID": 365, "StatID": 1, "Val": 87 }, { "StaffID": 365, "StatID": 14, "Val": 85 }, { "StaffID": 365, "StatID": 15, "Val": 86 }, { "StaffID": 365, "StatID": 16, "Val": 88 }, { "StaffID": 365, "StatID": 17, "Val": 86 }, { "StaffID": 366, "StatID": 11, "Val": 86 }, { "StaffID": 366, "StatID": 22, "Val": 92 }, { "StaffID": 366, "StatID": 23, "Val": 88 }, { "StaffID": 366, "StatID": 24, "Val": 90 }, { "StaffID": 367, "StatID": 11, "Val": 92 }, { "StaffID": 367, "StatID": 22, "Val": 90 }, { "StaffID": 367, "StatID": 23, "Val": 94 }, { "StaffID": 367, "StatID": 24, "Val": 92 }, { "StaffID": 368, "StatID": 11, "Val": 80 }, { "StaffID": 368, "StatID": 22, "Val": 78 }, { "StaffID": 368, "StatID": 23, "Val": 80 }, { "StaffID": 368, "StatID": 24, "Val": 84 }, { "StaffID": 369, "StatID": 19, "Val": 86 }, { "StaffID": 369, "StatID": 20, "Val": 85 }, { "StaffID": 369, "StatID": 26, "Val": 84 }, { "StaffID": 369, "StatID": 27, "Val": 83 }, { "StaffID": 369, "StatID": 28, "Val": 82 }, { "StaffID": 369, "StatID": 29, "Val": 83 }, { "StaffID": 369, "StatID": 30, "Val": 84 }, { "StaffID": 369, "StatID": 31, "Val": 85 }, { "StaffID": 370, "StatID": 11, "Val": 84 }, { "StaffID": 370, "StatID": 22, "Val": 86 }, { "StaffID": 370, "StatID": 23, "Val": 88 }, { "StaffID": 370, "StatID": 24, "Val": 88 }, { "StaffID": 374, "StatID": 2, "Val": 59 }, { "StaffID": 374, "StatID": 3, "Val": 63 }, { "StaffID": 374, "StatID": 4, "Val": 65 }, { "StaffID": 374, "StatID": 5, "Val": 64 }, { "StaffID": 374, "StatID": 6, "Val": 69 }, { "StaffID": 374, "StatID": 7, "Val": 54 }, { "StaffID": 374, "StatID": 8, "Val": 57 }, { "StaffID": 374, "StatID": 9, "Val": 61 }, { "StaffID": 374, "StatID": 10, "Val": 60 }, { "StaffID": 386, "StatID": 2, "Val": 53 }, { "StaffID": 386, "StatID": 3, "Val": 53 }, { "StaffID": 386, "StatID": 4, "Val": 55 }, { "StaffID": 386, "StatID": 5, "Val": 52 }, { "StaffID": 386, "StatID": 6, "Val": 59 }, { "StaffID": 386, "StatID": 8, "Val": 60 }, { "StaffID": 386, "StatID": 9, "Val": 51 }, { "StaffID": 386, "StatID": 10, "Val": 52 }, { "StaffID": 388, "StatID": 2, "Val": 57 }, { "StaffID": 388, "StatID": 3, "Val": 61 }, { "StaffID": 388, "StatID": 5, "Val": 47 }, { "StaffID": 388, "StatID": 6, "Val": 60 }, { "StaffID": 388, "StatID": 7, "Val": 56 }, { "StaffID": 388, "StatID": 8, "Val": 57 }, { "StaffID": 388, "StatID": 9, "Val": 63 }, { "StaffID": 388, "StatID": 10, "Val": 50 }, { "StaffID": 390, "StatID": 2, "Val": 61 }, { "StaffID": 390, "StatID": 3, "Val": 62 }, { "StaffID": 390, "StatID": 4, "Val": 57 }, { "StaffID": 390, "StatID": 5, "Val": 56 }, { "StaffID": 390, "StatID": 6, "Val": 60 }, { "StaffID": 390, "StatID": 7, "Val": 64 }, { "StaffID": 390, "StatID": 8, "Val": 65 }, { "StaffID": 390, "StatID": 9, "Val": 59 }, { "StaffID": 390, "StatID": 10, "Val": 63 }, { "StaffID": 391, "StatID": 0, "Val": 88 }, { "StaffID": 391, "StatID": 1, "Val": 85 }, { "StaffID": 391, "StatID": 14, "Val": 84 }, { "StaffID": 391, "StatID": 15, "Val": 80 }, { "StaffID": 391, "StatID": 16, "Val": 85 }, { "StaffID": 391, "StatID": 17, "Val": 89 }, { "StaffID": 392, "StatID": 11, "Val": 91 }, { "StaffID": 392, "StatID": 22, "Val": 90 }, { "StaffID": 392, "StatID": 23, "Val": 92 }, { "StaffID": 392, "StatID": 24, "Val": 91 }, { "StaffID": 393, "StatID": 13, "Val": 85 }, { "StaffID": 393, "StatID": 25, "Val": 84 }, { "StaffID": 393, "StatID": 43, "Val": 83 }, { "StaffID": 397, "StatID": 11, "Val": 84 }, { "StaffID": 397, "StatID": 22, "Val": 84 }, { "StaffID": 397, "StatID": 23, "Val": 84 }, { "StaffID": 397, "StatID": 24, "Val": 83 }, { "StaffID": 400, "StatID": 2, "Val": 60 }, { "StaffID": 400, "StatID": 3, "Val": 54 }, { "StaffID": 400, "StatID": 4, "Val": 60 }, { "StaffID": 400, "StatID": 5, "Val": 60 }, { "StaffID": 400, "StatID": 6, "Val": 57 }, { "StaffID": 400, "StatID": 7, "Val": 58 }, { "StaffID": 400, "StatID": 8, "Val": 55 }, { "StaffID": 400, "StatID": 9, "Val": 54 }, { "StaffID": 400, "StatID": 10, "Val": 58 }, { "StaffID": 401, "StatID": 2, "Val": 51 }, { "StaffID": 401, "StatID": 3, "Val": 52 }, { "StaffID": 401, "StatID": 4, "Val": 50 }, { "StaffID": 401, "StatID": 5, "Val": 52 }, { "StaffID": 401, "StatID": 6, "Val": 50 }, { "StaffID": 401, "StatID": 7, "Val": 53 }, { "StaffID": 401, "StatID": 8, "Val": 51 }, { "StaffID": 401, "StatID": 9, "Val": 49 }, { "StaffID": 401, "StatID": 10, "Val": 51 }, { "StaffID": 403, "StatID": 0, "Val": 83 }, { "StaffID": 403, "StatID": 1, "Val": 85 }, { "StaffID": 403, "StatID": 14, "Val": 84 }, { "StaffID": 403, "StatID": 15, "Val": 81 }, { "StaffID": 403, "StatID": 16, "Val": 83 }, { "StaffID": 403, "StatID": 17, "Val": 84 }, { "StaffID": 404, "StatID": 19, "Val": 84 }, { "StaffID": 404, "StatID": 20, "Val": 83 }, { "StaffID": 404, "StatID": 26, "Val": 82 }, { "StaffID": 404, "StatID": 27, "Val": 83 }, { "StaffID": 404, "StatID": 28, "Val": 84 }, { "StaffID": 404, "StatID": 29, "Val": 83 }, { "StaffID": 404, "StatID": 30, "Val": 82 }, { "StaffID": 404, "StatID": 31, "Val": 82 }, { "StaffID": 408, "StatID": 2, "Val": 51 }, { "StaffID": 408, "StatID": 3, "Val": 49 }, { "StaffID": 408, "StatID": 4, "Val": 60 }, { "StaffID": 408, "StatID": 5, "Val": 51 }, { "StaffID": 408, "StatID": 6, "Val": 52 }, { "StaffID": 408, "StatID": 7, "Val": 48 }, { "StaffID": 408, "StatID": 8, "Val": 47 }, { "StaffID": 408, "StatID": 9, "Val": 60 }, { "StaffID": 408, "StatID": 10, "Val": 52 }, { "StaffID": 413, "StatID": 2, "Val": 75 }, { "StaffID": 413, "StatID": 3, "Val": 78 }, { "StaffID": 413, "StatID": 4, "Val": 71 }, { "StaffID": 413, "StatID": 5, "Val": 72 }, { "StaffID": 413, "StatID": 6, "Val": 76 }, { "StaffID": 413, "StatID": 7, "Val": 76 }, { "StaffID": 413, "StatID": 8, "Val": 75 }, { "StaffID": 413, "StatID": 9, "Val": 79 }, { "StaffID": 413, "StatID": 10, "Val": 78 }, { "StaffID": 414, "StatID": 19, "Val": 85 }, { "StaffID": 414, "StatID": 20, "Val": 84 }, { "StaffID": 414, "StatID": 26, "Val": 86 }, { "StaffID": 414, "StatID": 27, "Val": 85 }, { "StaffID": 414, "StatID": 28, "Val": 84 }, { "StaffID": 414, "StatID": 29, "Val": 84 }, { "StaffID": 414, "StatID": 30, "Val": 85 }, { "StaffID": 414, "StatID": 31, "Val": 85 }, { "StaffID": 415, "StatID": 13, "Val": 80 }, { "StaffID": 415, "StatID": 25, "Val": 83 }, { "StaffID": 415, "StatID": 43, "Val": 89 }, { "StaffID": 416, "StatID": 2, "Val": 61 }, { "StaffID": 416, "StatID": 3, "Val": 44 }, { "StaffID": 416, "StatID": 4, "Val": 52 }, { "StaffID": 416, "StatID": 5, "Val": 50 }, { "StaffID": 416, "StatID": 6, "Val": 46 }, { "StaffID": 416, "StatID": 7, "Val": 59 }, { "StaffID": 416, "StatID": 8, "Val": 48 }, { "StaffID": 416, "StatID": 9, "Val": 47 }, { "StaffID": 416, "StatID": 10, "Val": 53 }, { "StaffID": 420, "StatID": 13, "Val": 70 }, { "StaffID": 420, "StatID": 25, "Val": 72 }, { "StaffID": 420, "StatID": 43, "Val": 70 }, { "StaffID": 421, "StatID": 25, "Val": 78 }, { "StaffID": 421, "StatID": 43, "Val": 77 }, { "StaffID": 422, "StatID": 13, "Val": 74 }, { "StaffID": 422, "StatID": 25, "Val": 78 }, { "StaffID": 422, "StatID": 43, "Val": 76 }, { "StaffID": 423, "StatID": 13, "Val": 75 }, { "StaffID": 423, "StatID": 25, "Val": 76 }, { "StaffID": 423, "StatID": 43, "Val": 75 }, { "StaffID": 424, "StatID": 13, "Val": 73 }, { "StaffID": 424, "StatID": 25, "Val": 76 }, { "StaffID": 424, "StatID": 43, "Val": 79 }, { "StaffID": 425, "StatID": 13, "Val": 76 }, { "StaffID": 425, "StatID": 25, "Val": 78 }, { "StaffID": 425, "StatID": 43, "Val": 77 }, { "StaffID": 426, "StatID": 13, "Val": 75 }, { "StaffID": 426, "StatID": 25, "Val": 76 }, { "StaffID": 426, "StatID": 43, "Val": 76 }, { "StaffID": 427, "StatID": 25, "Val": 77 }, { "StaffID": 427, "StatID": 43, "Val": 74 }, { "StaffID": 428, "StatID": 13, "Val": 74 }, { "StaffID": 428, "StatID": 25, "Val": 74 }, { "StaffID": 428, "StatID": 43, "Val": 77 }, { "StaffID": 429, "StatID": 13, "Val": 74 }, { "StaffID": 429, "StatID": 25, "Val": 75 }, { "StaffID": 429, "StatID": 43, "Val": 75 }, { "StaffID": 430, "StatID": 13, "Val": 76 }, { "StaffID": 430, "StatID": 25, "Val": 75 }, { "StaffID": 430, "StatID": 43, "Val": 76 }, { "StaffID": 431, "StatID": 25, "Val": 76 }, { "StaffID": 431, "StatID": 43, "Val": 73 }, { "StaffID": 432, "StatID": 13, "Val": 71 }, { "StaffID": 432, "StatID": 25, "Val": 70 }, { "StaffID": 432, "StatID": 43, "Val": 74 }, { "StaffID": 433, "StatID": 13, "Val": 74 }, { "StaffID": 433, "StatID": 25, "Val": 75 }, { "StaffID": 433, "StatID": 43, "Val": 73 }, { "StaffID": 434, "StatID": 13, "Val": 62 }, { "StaffID": 434, "StatID": 43, "Val": 43 }, { "StaffID": 435, "StatID": 13, "Val": 76 }, { "StaffID": 435, "StatID": 25, "Val": 74 }, { "StaffID": 435, "StatID": 43, "Val": 76 }, { "StaffID": 436, "StatID": 2, "Val": 47 }, { "StaffID": 436, "StatID": 4, "Val": 50 }, { "StaffID": 437, "StatID": 2, "Val": 50 }, { "StaffID": 437, "StatID": 4, "Val": 55 }, { "StaffID": 438, "StatID": 3, "Val": 47 }, { "StaffID": 438, "StatID": 4, "Val": 49 }, { "StaffID": 438, "StatID": 5, "Val": 50 }, { "StaffID": 436, "StatID": 5, "Val": 52 }, { "StaffID": 436, "StatID": 6, "Val": 47 }, { "StaffID": 436, "StatID": 7, "Val": 53 }, { "StaffID": 436, "StatID": 8, "Val": 54 }, { "StaffID": 436, "StatID": 9, "Val": 55 }, { "StaffID": 436, "StatID": 10, "Val": 43 }, { "StaffID": 437, "StatID": 5, "Val": 52 }, { "StaffID": 437, "StatID": 6, "Val": 51 }, { "StaffID": 437, "StatID": 7, "Val": 53 }, { "StaffID": 437, "StatID": 8, "Val": 52 }, { "StaffID": 437, "StatID": 9, "Val": 53 }, { "StaffID": 437, "StatID": 10, "Val": 48 }, { "StaffID": 438, "StatID": 2, "Val": 45 }, { "StaffID": 438, "StatID": 6, "Val": 51 }, { "StaffID": 438, "StatID": 7, "Val": 43 }, { "StaffID": 438, "StatID": 8, "Val": 44 }, { "StaffID": 438, "StatID": 9, "Val": 45 }, { "StaffID": 536, "StatID": 19, "Val": 79 }, { "StaffID": 536, "StatID": 20, "Val": 71 }, { "StaffID": 536, "StatID": 26, "Val": 77 }, { "StaffID": 536, "StatID": 27, "Val": 59 }, { "StaffID": 536, "StatID": 28, "Val": 59 }, { "StaffID": 536, "StatID": 29, "Val": 66 }, { "StaffID": 536, "StatID": 30, "Val": 70 }, { "StaffID": 536, "StatID": 31, "Val": 74 }, { "StaffID": 537, "StatID": 19, "Val": 72 }, { "StaffID": 537, "StatID": 20, "Val": 77 }, { "StaffID": 537, "StatID": 26, "Val": 68 }, { "StaffID": 537, "StatID": 27, "Val": 79 }, { "StaffID": 537, "StatID": 28, "Val": 79 }, { "StaffID": 537, "StatID": 29, "Val": 73 }, { "StaffID": 537, "StatID": 30, "Val": 65 }, { "StaffID": 537, "StatID": 31, "Val": 57 }, { "StaffID": 538, "StatID": 19, "Val": 63 }, { "StaffID": 538, "StatID": 20, "Val": 60 }, { "StaffID": 538, "StatID": 26, "Val": 77 }, { "StaffID": 538, "StatID": 27, "Val": 59 }, { "StaffID": 538, "StatID": 28, "Val": 85 }, { "StaffID": 538, "StatID": 29, "Val": 84 }, { "StaffID": 538, "StatID": 30, "Val": 84 }, { "StaffID": 538, "StatID": 31, "Val": 73 }, { "StaffID": 539, "StatID": 0, "Val": 78 }, { "StaffID": 539, "StatID": 1, "Val": 85 }, { "StaffID": 539, "StatID": 14, "Val": 76 }, { "StaffID": 539, "StatID": 15, "Val": 61 }, { "StaffID": 539, "StatID": 16, "Val": 56 }, { "StaffID": 539, "StatID": 17, "Val": 72 }, { "StaffID": 540, "StatID": 0, "Val": 66 }, { "StaffID": 540, "StatID": 1, "Val": 61 }, { "StaffID": 540, "StatID": 14, "Val": 83 }, { "StaffID": 540, "StatID": 15, "Val": 56 }, { "StaffID": 540, "StatID": 16, "Val": 66 }, { "StaffID": 540, "StatID": 17, "Val": 61 }, { "StaffID": 541, "StatID": 0, "Val": 72 }, { "StaffID": 541, "StatID": 1, "Val": 75 }, { "StaffID": 541, "StatID": 14, "Val": 56 }, { "StaffID": 541, "StatID": 15, "Val": 81 }, { "StaffID": 541, "StatID": 16, "Val": 72 }, { "StaffID": 541, "StatID": 17, "Val": 69 }, { "StaffID": 542, "StatID": 11, "Val": 66 }, { "StaffID": 542, "StatID": 22, "Val": 80 }, { "StaffID": 542, "StatID": 23, "Val": 83 }, { "StaffID": 542, "StatID": 24, "Val": 69 }, { "StaffID": 543, "StatID": 11, "Val": 70 }, { "StaffID": 543, "StatID": 22, "Val": 73 }, { "StaffID": 543, "StatID": 23, "Val": 55 }, { "StaffID": 543, "StatID": 24, "Val": 83 }, { "StaffID": 544, "StatID": 11, "Val": 71 }, { "StaffID": 544, "StatID": 22, "Val": 62 }, { "StaffID": 544, "StatID": 23, "Val": 76 }, { "StaffID": 544, "StatID": 24, "Val": 68 }, { "StaffID": 545, "StatID": 11, "Val": 83 }, { "StaffID": 545, "StatID": 22, "Val": 60 }, { "StaffID": 545, "StatID": 23, "Val": 85 }, { "StaffID": 545, "StatID": 24, "Val": 72 }, { "StaffID": 546, "StatID": 11, "Val": 57 }, { "StaffID": 546, "StatID": 22, "Val": 72 }, { "StaffID": 546, "StatID": 23, "Val": 60 }, { "StaffID": 546, "StatID": 24, "Val": 73 }, { "StaffID": 547, "StatID": 2, "Val": 51 }, { "StaffID": 547, "StatID": 3, "Val": 50 }, { "StaffID": 547, "StatID": 4, "Val": 49 }, { "StaffID": 547, "StatID": 5, "Val": 66 }, { "StaffID": 547, "StatID": 6, "Val": 66 }, { "StaffID": 547, "StatID": 7, "Val": 67 }, { "StaffID": 547, "StatID": 8, "Val": 59 }, { "StaffID": 547, "StatID": 9, "Val": 68 }, { "StaffID": 547, "StatID": 10, "Val": 58 }, { "StaffID": 548, "StatID": 2, "Val": 66 }, { "StaffID": 548, "StatID": 3, "Val": 48 }, { "StaffID": 548, "StatID": 4, "Val": 67 }, { "StaffID": 548, "StatID": 5, "Val": 62 }, { "StaffID": 548, "StatID": 6, "Val": 47 }, { "StaffID": 548, "StatID": 7, "Val": 53 }, { "StaffID": 548, "StatID": 8, "Val": 50 }, { "StaffID": 548, "StatID": 9, "Val": 65 }, { "StaffID": 548, "StatID": 10, "Val": 46 }, { "StaffID": 549, "StatID": 2, "Val": 62 }, { "StaffID": 549, "StatID": 3, "Val": 57 }, { "StaffID": 549, "StatID": 4, "Val": 51 }, { "StaffID": 549, "StatID": 5, "Val": 55 }, { "StaffID": 549, "StatID": 6, "Val": 58 }, { "StaffID": 549, "StatID": 7, "Val": 62 }, { "StaffID": 549, "StatID": 8, "Val": 60 }, { "StaffID": 549, "StatID": 9, "Val": 67 }, { "StaffID": 549, "StatID": 10, "Val": 65 }, { "StaffID": 550, "StatID": 2, "Val": 57 }, { "StaffID": 550, "StatID": 3, "Val": 69 }, { "StaffID": 550, "StatID": 4, "Val": 57 }, { "StaffID": 550, "StatID": 5, "Val": 64 }, { "StaffID": 550, "StatID": 6, "Val": 45 }, { "StaffID": 550, "StatID": 7, "Val": 48 }, { "StaffID": 550, "StatID": 8, "Val": 53 }, { "StaffID": 550, "StatID": 9, "Val": 51 }, { "StaffID": 550, "StatID": 10, "Val": 54 }, { "StaffID": 551, "StatID": 2, "Val": 68 }, { "StaffID": 551, "StatID": 3, "Val": 54 }, { "StaffID": 551, "StatID": 4, "Val": 51 }, { "StaffID": 551, "StatID": 5, "Val": 55 }, { "StaffID": 551, "StatID": 6, "Val": 69 }, { "StaffID": 551, "StatID": 7, "Val": 56 }, { "StaffID": 551, "StatID": 8, "Val": 63 }, { "StaffID": 551, "StatID": 9, "Val": 66 }, { "StaffID": 551, "StatID": 10, "Val": 70 }, { "StaffID": 554, "StatID": 2, "Val": 0 }, { "StaffID": 554, "StatID": 3, "Val": 0 }, { "StaffID": 554, "StatID": 4, "Val": 0 }, { "StaffID": 554, "StatID": 5, "Val": 0 }, { "StaffID": 554, "StatID": 6, "Val": 0 }, { "StaffID": 554, "StatID": 7, "Val": 0 }, { "StaffID": 554, "StatID": 8, "Val": 0 }, { "StaffID": 554, "StatID": 9, "Val": 0 }, { "StaffID": 554, "StatID": 10, "Val": 0 }, { "StaffID": 555, "StatID": 13, "Val": 67 }, { "StaffID": 555, "StatID": 25, "Val": 67 }, { "StaffID": 555, "StatID": 43, "Val": 67 }, { "StaffID": 556, "StatID": 13, "Val": 0 }, { "StaffID": 556, "StatID": 25, "Val": 0 }, { "StaffID": 556, "StatID": 43, "Val": 0 }, { "StaffID": 557, "StatID": 11, "Val": 0 }, { "StaffID": 557, "StatID": 22, "Val": 0 }, { "StaffID": 557, "StatID": 23, "Val": 0 }, { "StaffID": 557, "StatID": 24, "Val": 0 }, { "StaffID": 567, "StatID": 0, "Val": 56 }, { "StaffID": 567, "StatID": 1, "Val": 73 }, { "StaffID": 567, "StatID": 14, "Val": 64 }, { "StaffID": 567, "StatID": 15, "Val": 82 }, { "StaffID": 567, "StatID": 16, "Val": 77 }, { "StaffID": 567, "StatID": 17, "Val": 82 }, { "StaffID": 568, "StatID": 19, "Val": 0 }, { "StaffID": 568, "StatID": 20, "Val": 0 }, { "StaffID": 568, "StatID": 26, "Val": 0 }, { "StaffID": 568, "StatID": 27, "Val": 0 }, { "StaffID": 568, "StatID": 28, "Val": 0 }, { "StaffID": 568, "StatID": 29, "Val": 0 }, { "StaffID": 568, "StatID": 30, "Val": 0 }, { "StaffID": 568, "StatID": 31, "Val": 0 }, { "StaffID": 146, "StatID": 13, "Val": 76 }, { "StaffID": 153, "StatID": 25, "Val": 79 }, { "StaffID": 173, "StatID": 43, "Val": 77 }, { "StaffID": 178, "StatID": 13, "Val": 78 }, { "StaffID": 181, "StatID": 13, "Val": 75 }, { "StaffID": 312, "StatID": 23, "Val": 74 }, { "StaffID": 436, "StatID": 3, "Val": 46 }, { "StaffID": 437, "StatID": 3, "Val": 49 }, { "StaffID": 438, "StatID": 10, "Val": 44 }, { "StaffID": 608, "StatID": 2, "Val": 0 }, { "StaffID": 608, "StatID": 3, "Val": 0 }, { "StaffID": 608, "StatID": 4, "Val": 0 }, { "StaffID": 608, "StatID": 5, "Val": 0 }, { "StaffID": 608, "StatID": 6, "Val": 0 }, { "StaffID": 608, "StatID": 7, "Val": 0 }, { "StaffID": 608, "StatID": 8, "Val": 0 }, { "StaffID": 608, "StatID": 9, "Val": 0 }, { "StaffID": 608, "StatID": 10, "Val": 0 }, { "StaffID": 622, "StatID": 13, "Val": 86 }, { "StaffID": 622, "StatID": 25, "Val": 87 }, { "StaffID": 622, "StatID": 43, "Val": 86 }, { "StaffID": 623, "StatID": 13, "Val": 87 }, { "StaffID": 623, "StatID": 25, "Val": 86 }, { "StaffID": 623, "StatID": 43, "Val": 88 }, { "StaffID": 624, "StatID": 13, "Val": 80 }, { "StaffID": 624, "StatID": 25, "Val": 82 }, { "StaffID": 624, "StatID": 43, "Val": 79 }, { "StaffID": 625, "StatID": 13, "Val": 85 }, { "StaffID": 625, "StatID": 25, "Val": 82 }, { "StaffID": 625, "StatID": 43, "Val": 83 }, { "StaffID": 626, "StatID": 13, "Val": 82 }, { "StaffID": 626, "StatID": 25, "Val": 79 }, { "StaffID": 626, "StatID": 43, "Val": 81 }, { "StaffID": 628, "StatID": 0, "Val": 90 }, { "StaffID": 628, "StatID": 1, "Val": 89 }, { "StaffID": 628, "StatID": 14, "Val": 88 }, { "StaffID": 628, "StatID": 15, "Val": 89 }, { "StaffID": 628, "StatID": 16, "Val": 91 }, { "StaffID": 628, "StatID": 17, "Val": 88 }, { "StaffID": 630, "StatID": 0, "Val": 85 }, { "StaffID": 630, "StatID": 1, "Val": 85 }, { "StaffID": 630, "StatID": 14, "Val": 84 }, { "StaffID": 630, "StatID": 15, "Val": 84 }, { "StaffID": 630, "StatID": 16, "Val": 88 }, { "StaffID": 630, "StatID": 17, "Val": 84 }, { "StaffID": 633, "StatID": 0, "Val": 80 }, { "StaffID": 633, "StatID": 1, "Val": 81 }, { "StaffID": 633, "StatID": 14, "Val": 80 }, { "StaffID": 633, "StatID": 15, "Val": 79 }, { "StaffID": 633, "StatID": 16, "Val": 81 }, { "StaffID": 633, "StatID": 17, "Val": 80 }, { "StaffID": 635, "StatID": 19, "Val": 87 }, { "StaffID": 635, "StatID": 20, "Val": 92 }, { "StaffID": 635, "StatID": 26, "Val": 92 }, { "StaffID": 635, "StatID": 27, "Val": 91 }, { "StaffID": 635, "StatID": 28, "Val": 90 }, { "StaffID": 635, "StatID": 29, "Val": 88 }, { "StaffID": 635, "StatID": 30, "Val": 91 }, { "StaffID": 635, "StatID": 31, "Val": 89 }, { "StaffID": 636, "StatID": 19, "Val": 83 }, { "StaffID": 636, "StatID": 20, "Val": 82 }, { "StaffID": 636, "StatID": 26, "Val": 83 }, { "StaffID": 636, "StatID": 27, "Val": 84 }, { "StaffID": 636, "StatID": 28, "Val": 83 }, { "StaffID": 636, "StatID": 29, "Val": 81 }, { "StaffID": 636, "StatID": 30, "Val": 84 }, { "StaffID": 636, "StatID": 31, "Val": 83 }, { "StaffID": 637, "StatID": 19, "Val": 87 }, { "StaffID": 637, "StatID": 20, "Val": 88 }, { "StaffID": 637, "StatID": 26, "Val": 89 }, { "StaffID": 637, "StatID": 27, "Val": 88 }, { "StaffID": 637, "StatID": 28, "Val": 87 }, { "StaffID": 637, "StatID": 29, "Val": 88 }, { "StaffID": 637, "StatID": 30, "Val": 87 }, { "StaffID": 637, "StatID": 31, "Val": 88 }, { "StaffID": 638, "StatID": 11, "Val": 85 }, { "StaffID": 638, "StatID": 22, "Val": 92 }, { "StaffID": 638, "StatID": 23, "Val": 86 }, { "StaffID": 638, "StatID": 24, "Val": 88 }, { "StaffID": 639, "StatID": 11, "Val": 82 }, { "StaffID": 639, "StatID": 22, "Val": 85 }, { "StaffID": 639, "StatID": 23, "Val": 83 }, { "StaffID": 639, "StatID": 24, "Val": 90 }, { "StaffID": 640, "StatID": 11, "Val": 82 }, { "StaffID": 640, "StatID": 22, "Val": 84 }, { "StaffID": 640, "StatID": 23, "Val": 84 }, { "StaffID": 640, "StatID": 24, "Val": 85 }, { "StaffID": 641, "StatID": 11, "Val": 88 }, { "StaffID": 641, "StatID": 22, "Val": 93 }, { "StaffID": 641, "StatID": 23, "Val": 88 }, { "StaffID": 641, "StatID": 24, "Val": 90 }, { "StaffID": 642, "StatID": 0, "Val": 90 }, { "StaffID": 642, "StatID": 1, "Val": 88 }, { "StaffID": 642, "StatID": 14, "Val": 89 }, { "StaffID": 642, "StatID": 15, "Val": 88 }, { "StaffID": 642, "StatID": 16, "Val": 89 }, { "StaffID": 642, "StatID": 17, "Val": 91 }, { "StaffID": 643, "StatID": 13, "Val": 86 }, { "StaffID": 643, "StatID": 25, "Val": 83 }, { "StaffID": 643, "StatID": 43, "Val": 81 }, { "StaffID": 644, "StatID": 13, "Val": 80 }, { "StaffID": 644, "StatID": 25, "Val": 79 }, { "StaffID": 644, "StatID": 43, "Val": 85 }, { "StaffID": 40, "StatID": 19, "Val": 88 }, { "StaffID": 33, "StatID": 0, "Val": 84 }, { "StaffID": 58, "StatID": 13, "Val": 83 }, { "StaffID": 119, "StatID": 2, "Val": 69 }, { "StaffID": 158, "StatID": 43, "Val": 78 }, { "StaffID": 159, "StatID": 13, "Val": 80 }, { "StaffID": 193, "StatID": 13, "Val": 72 }, { "StaffID": 313, "StatID": 24, "Val": 73 }, { "StaffID": 341, "StatID": 13, "Val": 75 }, { "StaffID": 361, "StatID": 25, "Val": 74 }, { "StaffID": 386, "StatID": 7, "Val": 58 }, { "StaffID": 388, "StatID": 4, "Val": 59 }, { "StaffID": 421, "StatID": 13, "Val": 75 }, { "StaffID": 427, "StatID": 13, "Val": 76 }, { "StaffID": 431, "StatID": 13, "Val": 74 }, { "StaffID": 434, "StatID": 25, "Val": 47 }, { "StaffID": 677, "StatID": 19, "Val": 88 }, { "StaffID": 677, "StatID": 20, "Val": 90 }, { "StaffID": 677, "StatID": 26, "Val": 92 }, { "StaffID": 677, "StatID": 27, "Val": 91 }, { "StaffID": 677, "StatID": 28, "Val": 89 }, { "StaffID": 677, "StatID": 29, "Val": 90 }, { "StaffID": 677, "StatID": 30, "Val": 91 }, { "StaffID": 677, "StatID": 31, "Val": 90 }, { "StaffID": 678, "StatID": 11, "Val": 84 }, { "StaffID": 678, "StatID": 22, "Val": 90 }, { "StaffID": 678, "StatID": 23, "Val": 84 }, { "StaffID": 678, "StatID": 24, "Val": 89 }, { "StaffID": 679, "StatID": 11, "Val": 82 }, { "StaffID": 679, "StatID": 22, "Val": 82 }, { "StaffID": 679, "StatID": 23, "Val": 86 }, { "StaffID": 679, "StatID": 24, "Val": 82 }, { "StaffID": 680, "StatID": 13, "Val": 84 }, { "StaffID": 680, "StatID": 25, "Val": 84 }, { "StaffID": 680, "StatID": 43, "Val": 84 }, { "StaffID": 681, "StatID": 0, "Val": 88 }, { "StaffID": 681, "StatID": 1, "Val": 86 }, { "StaffID": 681, "StatID": 14, "Val": 85 }, { "StaffID": 681, "StatID": 15, "Val": 87 }, { "StaffID": 681, "StatID": 16, "Val": 87 }, { "StaffID": 681, "StatID": 17, "Val": 87 }, { "StaffID": 682, "StatID": 13, "Val": 84 }, { "StaffID": 682, "StatID": 25, "Val": 82 }, { "StaffID": 682, "StatID": 43, "Val": 83 }, { "StaffID": 683, "StatID": 0, "Val": 87 }, { "StaffID": 683, "StatID": 1, "Val": 85 }, { "StaffID": 683, "StatID": 14, "Val": 84 }, { "StaffID": 683, "StatID": 15, "Val": 85 }, { "StaffID": 683, "StatID": 16, "Val": 86 }, { "StaffID": 683, "StatID": 17, "Val": 87 }, { "StaffID": 684, "StatID": 13, "Val": 83 }, { "StaffID": 684, "StatID": 25, "Val": 83 }, { "StaffID": 684, "StatID": 43, "Val": 84 }, { "StaffID": 395, "StatID": 13, "Val": 86 }, { "StaffID": 395, "StatID": 25, "Val": 87 }, { "StaffID": 395, "StatID": 43, "Val": 86 }, { "StaffID": 338, "StatID": 13, "Val": 72 }, { "StaffID": 338, "StatID": 25, "Val": 76 }, { "StaffID": 338, "StatID": 43, "Val": 72 }, { "StaffID": 156, "StatID": 13, "Val": 78 }, { "StaffID": 156, "StatID": 25, "Val": 79 }, { "StaffID": 156, "StatID": 43, "Val": 78 }, { "StaffID": 145, "StatID": 13, "Val": 74 }, { "StaffID": 145, "StatID": 25, "Val": 75 }, { "StaffID": 145, "StatID": 43, "Val": 74 }, { "StaffID": 685, "StatID": 2, "Val": 0 }, { "StaffID": 685, "StatID": 3, "Val": 0 }, { "StaffID": 685, "StatID": 4, "Val": 0 }, { "StaffID": 685, "StatID": 5, "Val": 0 }, { "StaffID": 685, "StatID": 6, "Val": 0 }, { "StaffID": 685, "StatID": 7, "Val": 0 }, { "StaffID": 685, "StatID": 8, "Val": 0 }, { "StaffID": 685, "StatID": 9, "Val": 0 }, { "StaffID": 685, "StatID": 10, "Val": 0 }, { "StaffID": 686, "StatID": 2, "Val": 0 }, { "StaffID": 686, "StatID": 3, "Val": 0 }, { "StaffID": 686, "StatID": 4, "Val": 0 }, { "StaffID": 686, "StatID": 5, "Val": 0 }, { "StaffID": 686, "StatID": 6, "Val": 0 }, { "StaffID": 686, "StatID": 7, "Val": 0 }, { "StaffID": 686, "StatID": 8, "Val": 0 }, { "StaffID": 686, "StatID": 9, "Val": 0 }, { "StaffID": 686, "StatID": 10, "Val": 0 }, { "StaffID": 687, "StatID": 2, "Val": 0 }, { "StaffID": 687, "StatID": 3, "Val": 0 }, { "StaffID": 687, "StatID": 4, "Val": 0 }, { "StaffID": 687, "StatID": 5, "Val": 0 }, { "StaffID": 687, "StatID": 6, "Val": 0 }, { "StaffID": 687, "StatID": 7, "Val": 0 }, { "StaffID": 687, "StatID": 8, "Val": 0 }, { "StaffID": 687, "StatID": 9, "Val": 0 }, { "StaffID": 687, "StatID": 10, "Val": 0 }, { "StaffID": 688, "StatID": 2, "Val": 0 }, { "StaffID": 688, "StatID": 3, "Val": 0 }, { "StaffID": 688, "StatID": 4, "Val": 0 }, { "StaffID": 688, "StatID": 5, "Val": 0 }, { "StaffID": 688, "StatID": 6, "Val": 0 }, { "StaffID": 688, "StatID": 7, "Val": 0 }, { "StaffID": 688, "StatID": 8, "Val": 0 }, { "StaffID": 688, "StatID": 9, "Val": 0 }, { "StaffID": 688, "StatID": 10, "Val": 0 }, { "StaffID": 689, "StatID": 2, "Val": 0 }, { "StaffID": 689, "StatID": 3, "Val": 0 }, { "StaffID": 689, "StatID": 4, "Val": 0 }, { "StaffID": 689, "StatID": 5, "Val": 0 }, { "StaffID": 689, "StatID": 6, "Val": 0 }, { "StaffID": 689, "StatID": 7, "Val": 0 }, { "StaffID": 689, "StatID": 8, "Val": 0 }, { "StaffID": 689, "StatID": 9, "Val": 0 }, { "StaffID": 689, "StatID": 10, "Val": 0 }, { "StaffID": 690, "StatID": 13, "Val": 0 }, { "StaffID": 690, "StatID": 25, "Val": 0 }, { "StaffID": 690, "StatID": 43, "Val": 0 }, { "StaffID": 691, "StatID": 13, "Val": 0 }, { "StaffID": 691, "StatID": 25, "Val": 0 }, { "StaffID": 691, "StatID": 43, "Val": 0 }, { "StaffID": 692, "StatID": 13, "Val": 0 }, { "StaffID": 692, "StatID": 25, "Val": 0 }, { "StaffID": 692, "StatID": 43, "Val": 0 }, { "StaffID": 693, "StatID": 13, "Val": 0 }, { "StaffID": 693, "StatID": 25, "Val": 0 }, { "StaffID": 693, "StatID": 43, "Val": 0 }, { "StaffID": 694, "StatID": 13, "Val": 0 }, { "StaffID": 694, "StatID": 25, "Val": 0 }, { "StaffID": 694, "StatID": 43, "Val": 0 }, { "StaffID": 695, "StatID": 13, "Val": 0 }, { "StaffID": 695, "StatID": 25, "Val": 0 }, { "StaffID": 695, "StatID": 43, "Val": 0 }, { "StaffID": 696, "StatID": 0, "Val": 0 }, { "StaffID": 696, "StatID": 1, "Val": 0 }, { "StaffID": 696, "StatID": 14, "Val": 0 }, { "StaffID": 696, "StatID": 15, "Val": 0 }, { "StaffID": 696, "StatID": 16, "Val": 0 }, { "StaffID": 696, "StatID": 17, "Val": 0 }, { "StaffID": 697, "StatID": 11, "Val": 0 }, { "StaffID": 697, "StatID": 22, "Val": 0 }, { "StaffID": 697, "StatID": 23, "Val": 0 }, { "StaffID": 697, "StatID": 24, "Val": 0 }, { "StaffID": 698, "StatID": 11, "Val": 0 }, { "StaffID": 698, "StatID": 22, "Val": 0 }, { "StaffID": 698, "StatID": 23, "Val": 0 }, { "StaffID": 698, "StatID": 24, "Val": 0 }, { "StaffID": 699, "StatID": 11, "Val": 0 }, { "StaffID": 699, "StatID": 22, "Val": 0 }, { "StaffID": 699, "StatID": 23, "Val": 0 }, { "StaffID": 699, "StatID": 24, "Val": 0 }, { "StaffID": 700, "StatID": 0, "Val": 0 }, { "StaffID": 700, "StatID": 1, "Val": 0 }, { "StaffID": 700, "StatID": 14, "Val": 0 }, { "StaffID": 700, "StatID": 15, "Val": 0 }, { "StaffID": 700, "StatID": 16, "Val": 0 }, { "StaffID": 700, "StatID": 17, "Val": 0 }, { "StaffID": 701, "StatID": 13, "Val": 0 }, { "StaffID": 701, "StatID": 25, "Val": 0 }, { "StaffID": 701, "StatID": 43, "Val": 0 }, { "StaffID": 702, "StatID": 13, "Val": 0 }, { "StaffID": 702, "StatID": 25, "Val": 0 }, { "StaffID": 702, "StatID": 43, "Val": 0 }, { "StaffID": 703, "StatID": 19, "Val": 0 }, { "StaffID": 703, "StatID": 20, "Val": 0 }, { "StaffID": 703, "StatID": 26, "Val": 0 }, { "StaffID": 703, "StatID": 27, "Val": 0 }, { "StaffID": 703, "StatID": 28, "Val": 0 }, { "StaffID": 703, "StatID": 29, "Val": 0 }, { "StaffID": 703, "StatID": 30, "Val": 0 }, { "StaffID": 703, "StatID": 31, "Val": 0 }, { "StaffID": 704, "StatID": 11, "Val": 0 }, { "StaffID": 704, "StatID": 22, "Val": 0 }, { "StaffID": 704, "StatID": 23, "Val": 0 }, { "StaffID": 704, "StatID": 24, "Val": 0 }, { "StaffID": 705, "StatID": 11, "Val": 0 }, { "StaffID": 705, "StatID": 22, "Val": 0 }, { "StaffID": 705, "StatID": 23, "Val": 0 }, { "StaffID": 705, "StatID": 24, "Val": 0 }, { "StaffID": 706, "StatID": 13, "Val": 0 }, { "StaffID": 706, "StatID": 25, "Val": 0 }, { "StaffID": 706, "StatID": 43, "Val": 0 }, { "StaffID": 707, "StatID": 0, "Val": 0 }, { "StaffID": 707, "StatID": 1, "Val": 0 }, { "StaffID": 707, "StatID": 14, "Val": 0 }, { "StaffID": 707, "StatID": 15, "Val": 0 }, { "StaffID": 707, "StatID": 16, "Val": 0 }, { "StaffID": 707, "StatID": 17, "Val": 0 }, { "StaffID": 708, "StatID": 13, "Val": 0 }, { "StaffID": 708, "StatID": 25, "Val": 0 }, { "StaffID": 708, "StatID": 43, "Val": 0 }, { "StaffID": 709, "StatID": 0, "Val": 0 }, { "StaffID": 709, "StatID": 1, "Val": 0 }, { "StaffID": 709, "StatID": 14, "Val": 0 }, { "StaffID": 709, "StatID": 15, "Val": 0 }, { "StaffID": 709, "StatID": 16, "Val": 0 }, { "StaffID": 709, "StatID": 17, "Val": 0 }, { "StaffID": 710, "StatID": 13, "Val": 0 }, { "StaffID": 710, "StatID": 25, "Val": 0 }, { "StaffID": 710, "StatID": 43, "Val": 0 }, { "StaffID": 711, "StatID": 2, "Val": 0 }, { "StaffID": 711, "StatID": 3, "Val": 0 }, { "StaffID": 711, "StatID": 4, "Val": 0 }, { "StaffID": 711, "StatID": 5, "Val": 0 }, { "StaffID": 711, "StatID": 6, "Val": 0 }, { "StaffID": 711, "StatID": 7, "Val": 0 }, { "StaffID": 711, "StatID": 8, "Val": 0 }, { "StaffID": 711, "StatID": 9, "Val": 0 }, { "StaffID": 711, "StatID": 10, "Val": 0 }, { "StaffID": 712, "StatID": 2, "Val": 0 }, { "StaffID": 712, "StatID": 3, "Val": 0 }, { "StaffID": 712, "StatID": 4, "Val": 0 }, { "StaffID": 712, "StatID": 5, "Val": 0 }, { "StaffID": 712, "StatID": 6, "Val": 0 }, { "StaffID": 712, "StatID": 7, "Val": 0 }, { "StaffID": 712, "StatID": 8, "Val": 0 }, { "StaffID": 712, "StatID": 9, "Val": 0 }, { "StaffID": 712, "StatID": 10, "Val": 0 }, { "StaffID": 713, "StatID": 2, "Val": 0 }, { "StaffID": 713, "StatID": 3, "Val": 0 }, { "StaffID": 713, "StatID": 4, "Val": 0 }, { "StaffID": 713, "StatID": 5, "Val": 0 }, { "StaffID": 713, "StatID": 6, "Val": 0 }, { "StaffID": 713, "StatID": 7, "Val": 0 }, { "StaffID": 713, "StatID": 8, "Val": 0 }, { "StaffID": 713, "StatID": 9, "Val": 0 }, { "StaffID": 713, "StatID": 10, "Val": 0 }, { "StaffID": 714, "StatID": 2, "Val": 0 }, { "StaffID": 714, "StatID": 3, "Val": 0 }, { "StaffID": 714, "StatID": 4, "Val": 0 }, { "StaffID": 714, "StatID": 5, "Val": 0 }, { "StaffID": 714, "StatID": 6, "Val": 0 }, { "StaffID": 714, "StatID": 7, "Val": 0 }, { "StaffID": 714, "StatID": 8, "Val": 0 }, { "StaffID": 714, "StatID": 9, "Val": 0 }, { "StaffID": 714, "StatID": 10, "Val": 0 }, { "StaffID": 715, "StatID": 2, "Val": 0 }, { "StaffID": 715, "StatID": 3, "Val": 0 }, { "StaffID": 715, "StatID": 4, "Val": 0 }, { "StaffID": 715, "StatID": 5, "Val": 0 }, { "StaffID": 715, "StatID": 6, "Val": 0 }, { "StaffID": 715, "StatID": 7, "Val": 0 }, { "StaffID": 715, "StatID": 8, "Val": 0 }, { "StaffID": 715, "StatID": 9, "Val": 0 }, { "StaffID": 715, "StatID": 10, "Val": 0 }, { "StaffID": 716, "StatID": 2, "Val": 0 }, { "StaffID": 716, "StatID": 3, "Val": 0 }, { "StaffID": 716, "StatID": 4, "Val": 0 }, { "StaffID": 716, "StatID": 5, "Val": 0 }, { "StaffID": 716, "StatID": 6, "Val": 0 }, { "StaffID": 716, "StatID": 7, "Val": 0 }, { "StaffID": 716, "StatID": 8, "Val": 0 }, { "StaffID": 716, "StatID": 9, "Val": 0 }, { "StaffID": 716, "StatID": 10, "Val": 0 }, { "StaffID": 43, "StatID": 26, "Val": 85 }, { "StaffID": 10, "StatID": 2, "Val": 96 }, { "StaffID": 10, "StatID": 3, "Val": 94 }, { "StaffID": 10, "StatID": 4, "Val": 95 }, { "StaffID": 10, "StatID": 5, "Val": 93 }, { "StaffID": 10, "StatID": 6, "Val": 92 }, { "StaffID": 10, "StatID": 7, "Val": 92 }, { "StaffID": 10, "StatID": 8, "Val": 93 }, { "StaffID": 10, "StatID": 9, "Val": 95 }, { "StaffID": 10, "StatID": 10, "Val": 92 }, { "StaffID": 2, "StatID": 2, "Val": 93 }, { "StaffID": 2, "StatID": 3, "Val": 95 }, { "StaffID": 2, "StatID": 4, "Val": 88 }, { "StaffID": 2, "StatID": 5, "Val": 86 }, { "StaffID": 2, "StatID": 6, "Val": 88 }, { "StaffID": 2, "StatID": 7, "Val": 92 }, { "StaffID": 2, "StatID": 8, "Val": 88 }, { "StaffID": 2, "StatID": 9, "Val": 92 }, { "StaffID": 2, "StatID": 10, "Val": 88 }, { "StaffID": 23, "StatID": 2, "Val": 90 }, { "StaffID": 23, "StatID": 3, "Val": 94 }, { "StaffID": 23, "StatID": 4, "Val": 87 }, { "StaffID": 23, "StatID": 5, "Val": 86 }, { "StaffID": 23, "StatID": 6, "Val": 91 }, { "StaffID": 23, "StatID": 7, "Val": 85 }, { "StaffID": 23, "StatID": 8, "Val": 89 }, { "StaffID": 23, "StatID": 9, "Val": 93 }, { "StaffID": 23, "StatID": 10, "Val": 94 }, { "StaffID": 12, "StatID": 2, "Val": 90 }, { "StaffID": 12, "StatID": 3, "Val": 88 }, { "StaffID": 12, "StatID": 4, "Val": 88 }, { "StaffID": 12, "StatID": 5, "Val": 93 }, { "StaffID": 12, "StatID": 6, "Val": 90 }, { "StaffID": 12, "StatID": 7, "Val": 93 }, { "StaffID": 12, "StatID": 8, "Val": 84 }, { "StaffID": 12, "StatID": 9, "Val": 91 }, { "StaffID": 12, "StatID": 10, "Val": 89 }, { "StaffID": 77, "StatID": 2, "Val": 90 }, { "StaffID": 77, "StatID": 3, "Val": 88 }, { "StaffID": 77, "StatID": 4, "Val": 90 }, { "StaffID": 77, "StatID": 5, "Val": 90 }, { "StaffID": 77, "StatID": 6, "Val": 91 }, { "StaffID": 77, "StatID": 7, "Val": 90 }, { "StaffID": 77, "StatID": 8, "Val": 90 }, { "StaffID": 77, "StatID": 9, "Val": 88 }, { "StaffID": 77, "StatID": 10, "Val": 87 }, { "StaffID": 102, "StatID": 2, "Val": 87 }, { "StaffID": 102, "StatID": 3, "Val": 86 }, { "StaffID": 102, "StatID": 4, "Val": 90 }, { "StaffID": 102, "StatID": 5, "Val": 91 }, { "StaffID": 102, "StatID": 6, "Val": 88 }, { "StaffID": 102, "StatID": 7, "Val": 89 }, { "StaffID": 102, "StatID": 8, "Val": 86 }, { "StaffID": 102, "StatID": 9, "Val": 88 }, { "StaffID": 102, "StatID": 10, "Val": 89 }, { "StaffID": 1, "StatID": 2, "Val": 87 }, { "StaffID": 1, "StatID": 3, "Val": 87 }, { "StaffID": 1, "StatID": 4, "Val": 89 }, { "StaffID": 1, "StatID": 5, "Val": 94 }, { "StaffID": 1, "StatID": 6, "Val": 93 }, { "StaffID": 1, "StatID": 7, "Val": 87 }, { "StaffID": 1, "StatID": 8, "Val": 84 }, { "StaffID": 1, "StatID": 9, "Val": 88 }, { "StaffID": 1, "StatID": 10, "Val": 89 }, { "StaffID": 11, "StatID": 2, "Val": 84 }, { "StaffID": 11, "StatID": 3, "Val": 87 }, { "StaffID": 11, "StatID": 4, "Val": 88 }, { "StaffID": 11, "StatID": 5, "Val": 90 }, { "StaffID": 11, "StatID": 6, "Val": 87 }, { "StaffID": 11, "StatID": 7, "Val": 83 }, { "StaffID": 11, "StatID": 8, "Val": 84 }, { "StaffID": 11, "StatID": 9, "Val": 84 }, { "StaffID": 11, "StatID": 10, "Val": 88 }, { "StaffID": 15, "StatID": 2, "Val": 89 }, { "StaffID": 15, "StatID": 3, "Val": 88 }, { "StaffID": 15, "StatID": 4, "Val": 83 }, { "StaffID": 15, "StatID": 5, "Val": 82 }, { "StaffID": 15, "StatID": 6, "Val": 85 }, { "StaffID": 15, "StatID": 7, "Val": 85 }, { "StaffID": 15, "StatID": 8, "Val": 84 }, { "StaffID": 15, "StatID": 9, "Val": 84 }, { "StaffID": 15, "StatID": 10, "Val": 84 }, { "StaffID": 3, "StatID": 2, "Val": 84 }, { "StaffID": 3, "StatID": 3, "Val": 83 }, { "StaffID": 3, "StatID": 4, "Val": 87 }, { "StaffID": 3, "StatID": 5, "Val": 86 }, { "StaffID": 3, "StatID": 6, "Val": 90 }, { "StaffID": 3, "StatID": 7, "Val": 84 }, { "StaffID": 3, "StatID": 8, "Val": 84 }, { "StaffID": 3, "StatID": 9, "Val": 85 }, { "StaffID": 3, "StatID": 10, "Val": 84 }, { "StaffID": 83, "StatID": 2, "Val": 84 }, { "StaffID": 83, "StatID": 3, "Val": 88 }, { "StaffID": 83, "StatID": 4, "Val": 84 }, { "StaffID": 83, "StatID": 5, "Val": 84 }, { "StaffID": 83, "StatID": 6, "Val": 85 }, { "StaffID": 83, "StatID": 7, "Val": 80 }, { "StaffID": 83, "StatID": 8, "Val": 83 }, { "StaffID": 83, "StatID": 9, "Val": 83 }, { "StaffID": 83, "StatID": 10, "Val": 87 }, { "StaffID": 376, "StatID": 2, "Val": 90 }, { "StaffID": 376, "StatID": 3, "Val": 88 }, { "StaffID": 376, "StatID": 4, "Val": 84 }, { "StaffID": 376, "StatID": 5, "Val": 80 }, { "StaffID": 376, "StatID": 6, "Val": 86 }, { "StaffID": 376, "StatID": 7, "Val": 85 }, { "StaffID": 376, "StatID": 8, "Val": 80 }, { "StaffID": 376, "StatID": 9, "Val": 91 }, { "StaffID": 376, "StatID": 10, "Val": 84 }, { "StaffID": 17, "StatID": 2, "Val": 82 }, { "StaffID": 17, "StatID": 3, "Val": 82 }, { "StaffID": 17, "StatID": 4, "Val": 84 }, { "StaffID": 17, "StatID": 5, "Val": 88 }, { "StaffID": 17, "StatID": 6, "Val": 86 }, { "StaffID": 17, "StatID": 7, "Val": 81 }, { "StaffID": 17, "StatID": 8, "Val": 83 }, { "StaffID": 17, "StatID": 9, "Val": 82 }, { "StaffID": 17, "StatID": 10, "Val": 82 }, { "StaffID": 144, "StatID": 2, "Val": 88 }, { "StaffID": 144, "StatID": 3, "Val": 85 }, { "StaffID": 144, "StatID": 4, "Val": 80 }, { "StaffID": 144, "StatID": 5, "Val": 78 }, { "StaffID": 144, "StatID": 6, "Val": 84 }, { "StaffID": 144, "StatID": 7, "Val": 89 }, { "StaffID": 144, "StatID": 8, "Val": 80 }, { "StaffID": 144, "StatID": 9, "Val": 90 }, { "StaffID": 144, "StatID": 10, "Val": 81 }, { "StaffID": 8, "StatID": 2, "Val": 82 }, { "StaffID": 8, "StatID": 3, "Val": 87 }, { "StaffID": 8, "StatID": 4, "Val": 80 }, { "StaffID": 8, "StatID": 5, "Val": 84 }, { "StaffID": 8, "StatID": 6, "Val": 82 }, { "StaffID": 8, "StatID": 7, "Val": 74 }, { "StaffID": 8, "StatID": 8, "Val": 76 }, { "StaffID": 8, "StatID": 9, "Val": 80 }, { "StaffID": 8, "StatID": 10, "Val": 87 }, { "StaffID": 81, "StatID": 2, "Val": 85 }, { "StaffID": 81, "StatID": 3, "Val": 84 }, { "StaffID": 81, "StatID": 4, "Val": 80 }, { "StaffID": 81, "StatID": 5, "Val": 79 }, { "StaffID": 81, "StatID": 6, "Val": 84 }, { "StaffID": 81, "StatID": 7, "Val": 86 }, { "StaffID": 81, "StatID": 8, "Val": 83 }, { "StaffID": 81, "StatID": 9, "Val": 85 }, { "StaffID": 81, "StatID": 10, "Val": 79 }, { "StaffID": 14, "StatID": 2, "Val": 82 }, { "StaffID": 14, "StatID": 3, "Val": 82 }, { "StaffID": 14, "StatID": 4, "Val": 82 }, { "StaffID": 14, "StatID": 5, "Val": 79 }, { "StaffID": 14, "StatID": 6, "Val": 83 }, { "StaffID": 14, "StatID": 7, "Val": 80 }, { "StaffID": 14, "StatID": 8, "Val": 88 }, { "StaffID": 14, "StatID": 9, "Val": 82 }, { "StaffID": 14, "StatID": 10, "Val": 81 }, { "StaffID": 142, "StatID": 2, "Val": 86 }, { "StaffID": 142, "StatID": 3, "Val": 84 }, { "StaffID": 142, "StatID": 4, "Val": 84 }, { "StaffID": 142, "StatID": 5, "Val": 79 }, { "StaffID": 142, "StatID": 6, "Val": 82 }, { "StaffID": 142, "StatID": 7, "Val": 88 }, { "StaffID": 142, "StatID": 8, "Val": 81 }, { "StaffID": 142, "StatID": 9, "Val": 85 }, { "StaffID": 142, "StatID": 10, "Val": 83 }, { "StaffID": 279, "StatID": 2, "Val": 83 }, { "StaffID": 279, "StatID": 3, "Val": 83 }, { "StaffID": 279, "StatID": 4, "Val": 82 }, { "StaffID": 279, "StatID": 5, "Val": 83 }, { "StaffID": 279, "StatID": 6, "Val": 79 }, { "StaffID": 279, "StatID": 7, "Val": 79 }, { "StaffID": 279, "StatID": 8, "Val": 78 }, { "StaffID": 279, "StatID": 9, "Val": 83 }, { "StaffID": 279, "StatID": 10, "Val": 85 }, { "StaffID": 95, "StatID": 2, "Val": 83 }, { "StaffID": 95, "StatID": 3, "Val": 82 }, { "StaffID": 95, "StatID": 4, "Val": 81 }, { "StaffID": 95, "StatID": 5, "Val": 79 }, { "StaffID": 95, "StatID": 6, "Val": 83 }, { "StaffID": 95, "StatID": 7, "Val": 87 }, { "StaffID": 95, "StatID": 8, "Val": 86 }, { "StaffID": 95, "StatID": 9, "Val": 84 }, { "StaffID": 95, "StatID": 10, "Val": 81 }, { "StaffID": 18, "StatID": 2, "Val": 81 }, { "StaffID": 18, "StatID": 3, "Val": 79 }, { "StaffID": 18, "StatID": 4, "Val": 79 }, { "StaffID": 18, "StatID": 5, "Val": 83 }, { "StaffID": 18, "StatID": 6, "Val": 83 }, { "StaffID": 18, "StatID": 7, "Val": 78 }, { "StaffID": 18, "StatID": 8, "Val": 80 }, { "StaffID": 18, "StatID": 9, "Val": 79 }, { "StaffID": 18, "StatID": 10, "Val": 79 }, { "StaffID": 248, "StatID": 2, "Val": 80 }, { "StaffID": 248, "StatID": 3, "Val": 80 }, { "StaffID": 248, "StatID": 4, "Val": 79 }, { "StaffID": 248, "StatID": 5, "Val": 77 }, { "StaffID": 248, "StatID": 6, "Val": 82 }, { "StaffID": 248, "StatID": 7, "Val": 85 }, { "StaffID": 248, "StatID": 8, "Val": 78 }, { "StaffID": 248, "StatID": 9, "Val": 84 }, { "StaffID": 248, "StatID": 10, "Val": 77 }, { "StaffID": 255, "StatID": 2, "Val": 80 }, { "StaffID": 255, "StatID": 3, "Val": 81 }, { "StaffID": 255, "StatID": 4, "Val": 78 }, { "StaffID": 255, "StatID": 5, "Val": 77 }, { "StaffID": 255, "StatID": 6, "Val": 78 }, { "StaffID": 255, "StatID": 7, "Val": 83 }, { "StaffID": 255, "StatID": 8, "Val": 82 }, { "StaffID": 255, "StatID": 9, "Val": 80 }, { "StaffID": 255, "StatID": 10, "Val": 77 }, { "StaffID": 20, "StatID": 2, "Val": 76 }, { "StaffID": 20, "StatID": 3, "Val": 80 }, { "StaffID": 20, "StatID": 4, "Val": 78 }, { "StaffID": 20, "StatID": 5, "Val": 77 }, { "StaffID": 20, "StatID": 6, "Val": 77 }, { "StaffID": 20, "StatID": 7, "Val": 76 }, { "StaffID": 20, "StatID": 8, "Val": 77 }, { "StaffID": 20, "StatID": 9, "Val": 77 }, { "StaffID": 20, "StatID": 10, "Val": 80 }, { "StaffID": 285, "StatID": 2, "Val": 80 }, { "StaffID": 285, "StatID": 3, "Val": 78 }, { "StaffID": 285, "StatID": 4, "Val": 76 }, { "StaffID": 285, "StatID": 5, "Val": 75 }, { "StaffID": 285, "StatID": 6, "Val": 76 }, { "StaffID": 285, "StatID": 7, "Val": 84 }, { "StaffID": 285, "StatID": 8, "Val": 78 }, { "StaffID": 285, "StatID": 9, "Val": 81 }, { "StaffID": 285, "StatID": 10, "Val": 75 }, { "StaffID": 87, "StatID": 2, "Val": 76 }, { "StaffID": 87, "StatID": 3, "Val": 78 }, { "StaffID": 87, "StatID": 4, "Val": 80 }, { "StaffID": 87, "StatID": 5, "Val": 78 }, { "StaffID": 87, "StatID": 6, "Val": 76 }, { "StaffID": 87, "StatID": 7, "Val": 74 }, { "StaffID": 87, "StatID": 8, "Val": 74 }, { "StaffID": 87, "StatID": 9, "Val": 76 }, { "StaffID": 87, "StatID": 10, "Val": 79 }, { "StaffID": 106, "StatID": 2, "Val": 75 }, { "StaffID": 106, "StatID": 3, "Val": 80 }, { "StaffID": 106, "StatID": 4, "Val": 79 }, { "StaffID": 106, "StatID": 5, "Val": 76 }, { "StaffID": 106, "StatID": 6, "Val": 75 }, { "StaffID": 106, "StatID": 7, "Val": 74 }, { "StaffID": 106, "StatID": 8, "Val": 74 }, { "StaffID": 106, "StatID": 9, "Val": 76 }, { "StaffID": 106, "StatID": 10, "Val": 79 }, { "StaffID": 105, "StatID": 2, "Val": 74 }, { "StaffID": 105, "StatID": 3, "Val": 75 }, { "StaffID": 105, "StatID": 4, "Val": 80 }, { "StaffID": 105, "StatID": 5, "Val": 82 }, { "StaffID": 105, "StatID": 6, "Val": 79 }, { "StaffID": 105, "StatID": 7, "Val": 74 }, { "StaffID": 105, "StatID": 8, "Val": 75 }, { "StaffID": 105, "StatID": 9, "Val": 75 }, { "StaffID": 105, "StatID": 10, "Val": 79 }, { "StaffID": 281, "StatID": 2, "Val": 75 }, { "StaffID": 281, "StatID": 3, "Val": 76 }, { "StaffID": 281, "StatID": 4, "Val": 79 }, { "StaffID": 281, "StatID": 5, "Val": 79 }, { "StaffID": 281, "StatID": 6, "Val": 77 }, { "StaffID": 281, "StatID": 7, "Val": 75 }, { "StaffID": 281, "StatID": 8, "Val": 75 }, { "StaffID": 281, "StatID": 9, "Val": 76 }, { "StaffID": 281, "StatID": 10, "Val": 78 }, { "StaffID": 120, "StatID": 2, "Val": 75 }, { "StaffID": 120, "StatID": 3, "Val": 80 }, { "StaffID": 120, "StatID": 4, "Val": 76 }, { "StaffID": 120, "StatID": 5, "Val": 74 }, { "StaffID": 120, "StatID": 6, "Val": 76 }, { "StaffID": 120, "StatID": 7, "Val": 78 }, { "StaffID": 120, "StatID": 8, "Val": 75 }, { "StaffID": 120, "StatID": 9, "Val": 78 }, { "StaffID": 120, "StatID": 10, "Val": 75 }, { "StaffID": 645, "StatID": 2, "Val": 74 }, { "StaffID": 645, "StatID": 3, "Val": 72 }, { "StaffID": 645, "StatID": 4, "Val": 69 }, { "StaffID": 645, "StatID": 5, "Val": 69 }, { "StaffID": 645, "StatID": 6, "Val": 73 }, { "StaffID": 645, "StatID": 7, "Val": 80 }, { "StaffID": 645, "StatID": 8, "Val": 72 }, { "StaffID": 645, "StatID": 9, "Val": 76 }, { "StaffID": 645, "StatID": 10, "Val": 69 }, { "StaffID": 76, "StatID": 2, "Val": 73 }, { "StaffID": 76, "StatID": 3, "Val": 79 }, { "StaffID": 76, "StatID": 4, "Val": 77 }, { "StaffID": 76, "StatID": 5, "Val": 76 }, { "StaffID": 76, "StatID": 6, "Val": 75 }, { "StaffID": 76, "StatID": 7, "Val": 72 }, { "StaffID": 76, "StatID": 8, "Val": 73 }, { "StaffID": 76, "StatID": 9, "Val": 74 }, { "StaffID": 76, "StatID": 10, "Val": 78 }, { "StaffID": 373, "StatID": 2, "Val": 80 }, { "StaffID": 373, "StatID": 3, "Val": 79 }, { "StaffID": 373, "StatID": 4, "Val": 73 }, { "StaffID": 373, "StatID": 5, "Val": 73 }, { "StaffID": 373, "StatID": 6, "Val": 75 }, { "StaffID": 373, "StatID": 7, "Val": 80 }, { "StaffID": 373, "StatID": 8, "Val": 73 }, { "StaffID": 373, "StatID": 9, "Val": 83 }, { "StaffID": 373, "StatID": 10, "Val": 74 }, { "StaffID": 107, "StatID": 2, "Val": 76 }, { "StaffID": 107, "StatID": 3, "Val": 79 }, { "StaffID": 107, "StatID": 4, "Val": 77 }, { "StaffID": 107, "StatID": 5, "Val": 74 }, { "StaffID": 107, "StatID": 6, "Val": 74 }, { "StaffID": 107, "StatID": 7, "Val": 72 }, { "StaffID": 107, "StatID": 8, "Val": 73 }, { "StaffID": 107, "StatID": 9, "Val": 74 }, { "StaffID": 107, "StatID": 10, "Val": 77 }, { "StaffID": 135, "StatID": 2, "Val": 75 }, { "StaffID": 135, "StatID": 3, "Val": 75 }, { "StaffID": 135, "StatID": 4, "Val": 77 }, { "StaffID": 135, "StatID": 5, "Val": 75 }, { "StaffID": 135, "StatID": 6, "Val": 76 }, { "StaffID": 135, "StatID": 7, "Val": 75 }, { "StaffID": 135, "StatID": 8, "Val": 79 }, { "StaffID": 135, "StatID": 9, "Val": 76 }, { "StaffID": 135, "StatID": 10, "Val": 76 }, { "StaffID": 22, "StatID": 2, "Val": 73 }, { "StaffID": 22, "StatID": 3, "Val": 74 }, { "StaffID": 22, "StatID": 4, "Val": 78 }, { "StaffID": 22, "StatID": 5, "Val": 80 }, { "StaffID": 22, "StatID": 6, "Val": 78 }, { "StaffID": 22, "StatID": 7, "Val": 72 }, { "StaffID": 22, "StatID": 8, "Val": 73 }, { "StaffID": 22, "StatID": 9, "Val": 73 }, { "StaffID": 22, "StatID": 10, "Val": 80 }, { "StaffID": 398, "StatID": 2, "Val": 73 }, { "StaffID": 398, "StatID": 3, "Val": 73 }, { "StaffID": 398, "StatID": 4, "Val": 77 }, { "StaffID": 398, "StatID": 5, "Val": 81 }, { "StaffID": 398, "StatID": 6, "Val": 81 }, { "StaffID": 398, "StatID": 7, "Val": 73 }, { "StaffID": 398, "StatID": 8, "Val": 74 }, { "StaffID": 398, "StatID": 9, "Val": 74 }, { "StaffID": 398, "StatID": 10, "Val": 78 }, { "StaffID": 74, "StatID": 2, "Val": 77 }, { "StaffID": 74, "StatID": 3, "Val": 74 }, { "StaffID": 74, "StatID": 4, "Val": 74 }, { "StaffID": 74, "StatID": 5, "Val": 74 }, { "StaffID": 74, "StatID": 6, "Val": 75 }, { "StaffID": 74, "StatID": 7, "Val": 78 }, { "StaffID": 74, "StatID": 8, "Val": 75 }, { "StaffID": 74, "StatID": 9, "Val": 80 }, { "StaffID": 74, "StatID": 10, "Val": 74 }, { "StaffID": 121, "StatID": 2, "Val": 76 }, { "StaffID": 121, "StatID": 3, "Val": 73 }, { "StaffID": 121, "StatID": 4, "Val": 74 }, { "StaffID": 121, "StatID": 5, "Val": 73 }, { "StaffID": 121, "StatID": 6, "Val": 75 }, { "StaffID": 121, "StatID": 7, "Val": 82 }, { "StaffID": 121, "StatID": 8, "Val": 76 }, { "StaffID": 121, "StatID": 9, "Val": 79 }, { "StaffID": 121, "StatID": 10, "Val": 74 }, { "StaffID": 289, "StatID": 2, "Val": 74 }, { "StaffID": 289, "StatID": 3, "Val": 73 }, { "StaffID": 289, "StatID": 4, "Val": 77 }, { "StaffID": 289, "StatID": 5, "Val": 80 }, { "StaffID": 289, "StatID": 6, "Val": 76 }, { "StaffID": 289, "StatID": 7, "Val": 72 }, { "StaffID": 289, "StatID": 8, "Val": 73 }, { "StaffID": 289, "StatID": 9, "Val": 73 }, { "StaffID": 289, "StatID": 10, "Val": 77 }, { "StaffID": 82, "StatID": 2, "Val": 73 }, { "StaffID": 82, "StatID": 3, "Val": 77 }, { "StaffID": 82, "StatID": 4, "Val": 75 }, { "StaffID": 82, "StatID": 5, "Val": 76 }, { "StaffID": 82, "StatID": 6, "Val": 74 }, { "StaffID": 82, "StatID": 7, "Val": 72 }, { "StaffID": 82, "StatID": 8, "Val": 73 }, { "StaffID": 82, "StatID": 9, "Val": 72 }, { "StaffID": 82, "StatID": 10, "Val": 78 }, { "StaffID": 604, "StatID": 2, "Val": 79 }, { "StaffID": 604, "StatID": 3, "Val": 78 }, { "StaffID": 604, "StatID": 4, "Val": 69 }, { "StaffID": 604, "StatID": 5, "Val": 69 }, { "StaffID": 604, "StatID": 6, "Val": 73 }, { "StaffID": 604, "StatID": 7, "Val": 78 }, { "StaffID": 604, "StatID": 8, "Val": 72 }, { "StaffID": 604, "StatID": 9, "Val": 79 }, { "StaffID": 604, "StatID": 10, "Val": 69 }, { "StaffID": 127, "StatID": 2, "Val": 74 }, { "StaffID": 127, "StatID": 3, "Val": 79 }, { "StaffID": 127, "StatID": 4, "Val": 72 }, { "StaffID": 127, "StatID": 5, "Val": 68 }, { "StaffID": 127, "StatID": 6, "Val": 71 }, { "StaffID": 127, "StatID": 7, "Val": 73 }, { "StaffID": 127, "StatID": 8, "Val": 72 }, { "StaffID": 127, "StatID": 9, "Val": 74 }, { "StaffID": 127, "StatID": 10, "Val": 77 }, { "StaffID": 130, "StatID": 2, "Val": 77 }, { "StaffID": 130, "StatID": 3, "Val": 78 }, { "StaffID": 130, "StatID": 4, "Val": 71 }, { "StaffID": 130, "StatID": 5, "Val": 68 }, { "StaffID": 130, "StatID": 6, "Val": 71 }, { "StaffID": 130, "StatID": 7, "Val": 79 }, { "StaffID": 130, "StatID": 8, "Val": 74 }, { "StaffID": 130, "StatID": 9, "Val": 76 }, { "StaffID": 130, "StatID": 10, "Val": 71 }, { "StaffID": 264, "StatID": 2, "Val": 70 }, { "StaffID": 264, "StatID": 3, "Val": 75 }, { "StaffID": 264, "StatID": 4, "Val": 75 }, { "StaffID": 264, "StatID": 5, "Val": 80 }, { "StaffID": 264, "StatID": 6, "Val": 81 }, { "StaffID": 264, "StatID": 7, "Val": 67 }, { "StaffID": 264, "StatID": 8, "Val": 69 }, { "StaffID": 264, "StatID": 9, "Val": 71 }, { "StaffID": 264, "StatID": 10, "Val": 78 }, { "StaffID": 399, "StatID": 2, "Val": 80 }, { "StaffID": 399, "StatID": 3, "Val": 78 }, { "StaffID": 399, "StatID": 4, "Val": 67 }, { "StaffID": 399, "StatID": 5, "Val": 66 }, { "StaffID": 399, "StatID": 6, "Val": 71 }, { "StaffID": 399, "StatID": 7, "Val": 77 }, { "StaffID": 399, "StatID": 8, "Val": 70 }, { "StaffID": 399, "StatID": 9, "Val": 79 }, { "StaffID": 399, "StatID": 10, "Val": 67 }, { "StaffID": 140, "StatID": 2, "Val": 71 }, { "StaffID": 140, "StatID": 3, "Val": 80 }, { "StaffID": 140, "StatID": 4, "Val": 72 }, { "StaffID": 140, "StatID": 5, "Val": 69 }, { "StaffID": 140, "StatID": 6, "Val": 70 }, { "StaffID": 140, "StatID": 7, "Val": 69 }, { "StaffID": 140, "StatID": 8, "Val": 69 }, { "StaffID": 140, "StatID": 9, "Val": 72 }, { "StaffID": 140, "StatID": 10, "Val": 78 }, { "StaffID": 242, "StatID": 2, "Val": 76 }, { "StaffID": 242, "StatID": 3, "Val": 75 }, { "StaffID": 242, "StatID": 4, "Val": 71 }, { "StaffID": 242, "StatID": 5, "Val": 69 }, { "StaffID": 242, "StatID": 6, "Val": 69 }, { "StaffID": 242, "StatID": 7, "Val": 80 }, { "StaffID": 242, "StatID": 8, "Val": 73 }, { "StaffID": 242, "StatID": 9, "Val": 75 }, { "StaffID": 242, "StatID": 10, "Val": 71 }, { "StaffID": 99, "StatID": 2, "Val": 68 }, { "StaffID": 99, "StatID": 3, "Val": 74 }, { "StaffID": 99, "StatID": 4, "Val": 75 }, { "StaffID": 99, "StatID": 5, "Val": 80 }, { "StaffID": 99, "StatID": 6, "Val": 75 }, { "StaffID": 99, "StatID": 7, "Val": 69 }, { "StaffID": 99, "StatID": 8, "Val": 72 }, { "StaffID": 99, "StatID": 9, "Val": 70 }, { "StaffID": 99, "StatID": 10, "Val": 75 }, { "StaffID": 322, "StatID": 2, "Val": 76 }, { "StaffID": 322, "StatID": 3, "Val": 77 }, { "StaffID": 322, "StatID": 4, "Val": 70 }, { "StaffID": 322, "StatID": 5, "Val": 67 }, { "StaffID": 322, "StatID": 6, "Val": 70 }, { "StaffID": 322, "StatID": 7, "Val": 79 }, { "StaffID": 322, "StatID": 8, "Val": 71 }, { "StaffID": 322, "StatID": 9, "Val": 77 }, { "StaffID": 322, "StatID": 10, "Val": 69 }, { "StaffID": 117, "StatID": 2, "Val": 70 }, { "StaffID": 117, "StatID": 3, "Val": 73 }, { "StaffID": 117, "StatID": 4, "Val": 74 }, { "StaffID": 117, "StatID": 5, "Val": 78 }, { "StaffID": 117, "StatID": 6, "Val": 73 }, { "StaffID": 117, "StatID": 7, "Val": 69 }, { "StaffID": 117, "StatID": 8, "Val": 70 }, { "StaffID": 117, "StatID": 9, "Val": 71 }, { "StaffID": 117, "StatID": 10, "Val": 75 }, { "StaffID": 245, "StatID": 2, "Val": 75 }, { "StaffID": 245, "StatID": 3, "Val": 74 }, { "StaffID": 245, "StatID": 4, "Val": 70 }, { "StaffID": 245, "StatID": 5, "Val": 66 }, { "StaffID": 245, "StatID": 6, "Val": 70 }, { "StaffID": 245, "StatID": 7, "Val": 82 }, { "StaffID": 245, "StatID": 8, "Val": 74 }, { "StaffID": 245, "StatID": 9, "Val": 75 }, { "StaffID": 245, "StatID": 10, "Val": 69 }, { "StaffID": 282, "StatID": 2, "Val": 76 }, { "StaffID": 282, "StatID": 3, "Val": 73 }, { "StaffID": 282, "StatID": 4, "Val": 71 }, { "StaffID": 282, "StatID": 5, "Val": 72 }, { "StaffID": 282, "StatID": 6, "Val": 71 }, { "StaffID": 282, "StatID": 7, "Val": 71 }, { "StaffID": 282, "StatID": 8, "Val": 71 }, { "StaffID": 282, "StatID": 9, "Val": 72 }, { "StaffID": 282, "StatID": 10, "Val": 73 }, { "StaffID": 88, "StatID": 2, "Val": 66 }, { "StaffID": 88, "StatID": 3, "Val": 78 }, { "StaffID": 88, "StatID": 4, "Val": 74 }, { "StaffID": 88, "StatID": 5, "Val": 79 }, { "StaffID": 88, "StatID": 6, "Val": 74 }, { "StaffID": 88, "StatID": 7, "Val": 66 }, { "StaffID": 88, "StatID": 8, "Val": 68 }, { "StaffID": 88, "StatID": 9, "Val": 68 }, { "StaffID": 88, "StatID": 10, "Val": 76 }, { "StaffID": 263, "StatID": 2, "Val": 70 }, { "StaffID": 263, "StatID": 3, "Val": 76 }, { "StaffID": 263, "StatID": 4, "Val": 74 }, { "StaffID": 263, "StatID": 5, "Val": 77 }, { "StaffID": 263, "StatID": 6, "Val": 73 }, { "StaffID": 263, "StatID": 7, "Val": 65 }, { "StaffID": 263, "StatID": 8, "Val": 68 }, { "StaffID": 263, "StatID": 9, "Val": 69 }, { "StaffID": 263, "StatID": 10, "Val": 74 }, { "StaffID": 286, "StatID": 2, "Val": 80 }, { "StaffID": 286, "StatID": 3, "Val": 75 }, { "StaffID": 286, "StatID": 4, "Val": 67 }, { "StaffID": 286, "StatID": 5, "Val": 66 }, { "StaffID": 286, "StatID": 6, "Val": 70 }, { "StaffID": 286, "StatID": 7, "Val": 82 }, { "StaffID": 286, "StatID": 8, "Val": 70 }, { "StaffID": 286, "StatID": 9, "Val": 76 }, { "StaffID": 286, "StatID": 10, "Val": 66 }, { "StaffID": 80, "StatID": 2, "Val": 68 }, { "StaffID": 80, "StatID": 3, "Val": 74 }, { "StaffID": 80, "StatID": 4, "Val": 73 }, { "StaffID": 80, "StatID": 5, "Val": 75 }, { "StaffID": 80, "StatID": 6, "Val": 73 }, { "StaffID": 80, "StatID": 7, "Val": 66 }, { "StaffID": 80, "StatID": 8, "Val": 68 }, { "StaffID": 80, "StatID": 9, "Val": 69 }, { "StaffID": 80, "StatID": 10, "Val": 74 }, { "StaffID": 603, "StatID": 2, "Val": 68 }, { "StaffID": 603, "StatID": 3, "Val": 73 }, { "StaffID": 603, "StatID": 4, "Val": 73 }, { "StaffID": 603, "StatID": 5, "Val": 78 }, { "StaffID": 603, "StatID": 6, "Val": 76 }, { "StaffID": 603, "StatID": 7, "Val": 66 }, { "StaffID": 603, "StatID": 8, "Val": 68 }, { "StaffID": 603, "StatID": 9, "Val": 68 }, { "StaffID": 603, "StatID": 10, "Val": 74 }, { "StaffID": 280, "StatID": 2, "Val": 79 }, { "StaffID": 280, "StatID": 3, "Val": 75 }, { "StaffID": 280, "StatID": 4, "Val": 66 }, { "StaffID": 280, "StatID": 5, "Val": 64 }, { "StaffID": 280, "StatID": 6, "Val": 69 }, { "StaffID": 280, "StatID": 7, "Val": 80 }, { "StaffID": 280, "StatID": 8, "Val": 69 }, { "StaffID": 280, "StatID": 9, "Val": 74 }, { "StaffID": 280, "StatID": 10, "Val": 66 }, { "StaffID": 378, "StatID": 2, "Val": 71 }, { "StaffID": 378, "StatID": 3, "Val": 71 }, { "StaffID": 378, "StatID": 4, "Val": 71 }, { "StaffID": 378, "StatID": 5, "Val": 73 }, { "StaffID": 378, "StatID": 6, "Val": 71 }, { "StaffID": 378, "StatID": 7, "Val": 72 }, { "StaffID": 378, "StatID": 8, "Val": 71 }, { "StaffID": 378, "StatID": 9, "Val": 72 }, { "StaffID": 378, "StatID": 10, "Val": 72 }, { "StaffID": 649, "StatID": 2, "Val": 78 }, { "StaffID": 649, "StatID": 3, "Val": 74 }, { "StaffID": 649, "StatID": 4, "Val": 64 }, { "StaffID": 649, "StatID": 5, "Val": 63 }, { "StaffID": 649, "StatID": 6, "Val": 67 }, { "StaffID": 649, "StatID": 7, "Val": 75 }, { "StaffID": 649, "StatID": 8, "Val": 68 }, { "StaffID": 649, "StatID": 9, "Val": 75 }, { "StaffID": 649, "StatID": 10, "Val": 64 }, { "StaffID": 132, "StatID": 2, "Val": 67 }, { "StaffID": 132, "StatID": 3, "Val": 72 }, { "StaffID": 132, "StatID": 4, "Val": 73 }, { "StaffID": 132, "StatID": 5, "Val": 77 }, { "StaffID": 132, "StatID": 6, "Val": 75 }, { "StaffID": 132, "StatID": 7, "Val": 64 }, { "StaffID": 132, "StatID": 8, "Val": 67 }, { "StaffID": 132, "StatID": 9, "Val": 67 }, { "StaffID": 132, "StatID": 10, "Val": 73 }, { "StaffID": 379, "StatID": 2, "Val": 72 }, { "StaffID": 379, "StatID": 3, "Val": 72 }, { "StaffID": 379, "StatID": 4, "Val": 67 }, { "StaffID": 379, "StatID": 5, "Val": 67 }, { "StaffID": 379, "StatID": 6, "Val": 70 }, { "StaffID": 379, "StatID": 7, "Val": 75 }, { "StaffID": 379, "StatID": 8, "Val": 70 }, { "StaffID": 379, "StatID": 9, "Val": 72 }, { "StaffID": 379, "StatID": 10, "Val": 67 }, { "StaffID": 301, "StatID": 2, "Val": 66 }, { "StaffID": 301, "StatID": 3, "Val": 69 }, { "StaffID": 301, "StatID": 4, "Val": 70 }, { "StaffID": 301, "StatID": 5, "Val": 75 }, { "StaffID": 301, "StatID": 6, "Val": 74 }, { "StaffID": 301, "StatID": 7, "Val": 69 }, { "StaffID": 301, "StatID": 8, "Val": 71 }, { "StaffID": 301, "StatID": 9, "Val": 69 }, { "StaffID": 301, "StatID": 10, "Val": 71 }, { "StaffID": 283, "StatID": 2, "Val": 71 }, { "StaffID": 283, "StatID": 3, "Val": 71 }, { "StaffID": 283, "StatID": 4, "Val": 66 }, { "StaffID": 283, "StatID": 5, "Val": 66 }, { "StaffID": 283, "StatID": 6, "Val": 69 }, { "StaffID": 283, "StatID": 7, "Val": 75 }, { "StaffID": 283, "StatID": 8, "Val": 69 }, { "StaffID": 283, "StatID": 9, "Val": 71 }, { "StaffID": 283, "StatID": 10, "Val": 66 }, { "StaffID": 411, "StatID": 2, "Val": 74 }, { "StaffID": 411, "StatID": 3, "Val": 70 }, { "StaffID": 411, "StatID": 4, "Val": 62 }, { "StaffID": 411, "StatID": 5, "Val": 62 }, { "StaffID": 411, "StatID": 6, "Val": 68 }, { "StaffID": 411, "StatID": 7, "Val": 76 }, { "StaffID": 411, "StatID": 8, "Val": 68 }, { "StaffID": 411, "StatID": 9, "Val": 75 }, { "StaffID": 411, "StatID": 10, "Val": 63 }, { "StaffID": 610, "StatID": 2, "Val": 74 }, { "StaffID": 610, "StatID": 3, "Val": 70 }, { "StaffID": 610, "StatID": 4, "Val": 64 }, { "StaffID": 610, "StatID": 5, "Val": 63 }, { "StaffID": 610, "StatID": 6, "Val": 68 }, { "StaffID": 610, "StatID": 7, "Val": 72 }, { "StaffID": 610, "StatID": 8, "Val": 68 }, { "StaffID": 610, "StatID": 9, "Val": 72 }, { "StaffID": 610, "StatID": 10, "Val": 64 }, { "StaffID": 377, "StatID": 2, "Val": 65 }, { "StaffID": 377, "StatID": 3, "Val": 69 }, { "StaffID": 377, "StatID": 4, "Val": 71 }, { "StaffID": 377, "StatID": 5, "Val": 75 }, { "StaffID": 377, "StatID": 6, "Val": 76 }, { "StaffID": 377, "StatID": 7, "Val": 66 }, { "StaffID": 377, "StatID": 8, "Val": 69 }, { "StaffID": 377, "StatID": 9, "Val": 68 }, { "StaffID": 377, "StatID": 10, "Val": 72 }, { "StaffID": 284, "StatID": 2, "Val": 68 }, { "StaffID": 284, "StatID": 3, "Val": 68 }, { "StaffID": 284, "StatID": 4, "Val": 68 }, { "StaffID": 284, "StatID": 5, "Val": 67 }, { "StaffID": 284, "StatID": 6, "Val": 70 }, { "StaffID": 284, "StatID": 7, "Val": 71 }, { "StaffID": 284, "StatID": 8, "Val": 71 }, { "StaffID": 284, "StatID": 9, "Val": 70 }, { "StaffID": 284, "StatID": 10, "Val": 68 }, { "StaffID": 375, "StatID": 2, "Val": 74 }, { "StaffID": 375, "StatID": 3, "Val": 70 }, { "StaffID": 375, "StatID": 4, "Val": 65 }, { "StaffID": 375, "StatID": 5, "Val": 64 }, { "StaffID": 375, "StatID": 6, "Val": 68 }, { "StaffID": 375, "StatID": 7, "Val": 74 }, { "StaffID": 375, "StatID": 8, "Val": 67 }, { "StaffID": 375, "StatID": 9, "Val": 73 }, { "StaffID": 375, "StatID": 10, "Val": 63 }, { "StaffID": 252, "StatID": 2, "Val": 65 }, { "StaffID": 252, "StatID": 3, "Val": 69 }, { "StaffID": 252, "StatID": 4, "Val": 69 }, { "StaffID": 252, "StatID": 5, "Val": 72 }, { "StaffID": 252, "StatID": 6, "Val": 71 }, { "StaffID": 252, "StatID": 7, "Val": 65 }, { "StaffID": 252, "StatID": 8, "Val": 68 }, { "StaffID": 252, "StatID": 9, "Val": 67 }, { "StaffID": 252, "StatID": 10, "Val": 68 }, { "StaffID": 288, "StatID": 2, "Val": 71 }, { "StaffID": 288, "StatID": 3, "Val": 69 }, { "StaffID": 288, "StatID": 4, "Val": 66 }, { "StaffID": 288, "StatID": 5, "Val": 65 }, { "StaffID": 288, "StatID": 6, "Val": 69 }, { "StaffID": 288, "StatID": 7, "Val": 75 }, { "StaffID": 288, "StatID": 8, "Val": 68 }, { "StaffID": 288, "StatID": 9, "Val": 72 }, { "StaffID": 288, "StatID": 10, "Val": 66 }, { "StaffID": 439, "StatID": 2, "Val": 72 }, { "StaffID": 439, "StatID": 3, "Val": 69 }, { "StaffID": 439, "StatID": 4, "Val": 63 }, { "StaffID": 439, "StatID": 5, "Val": 63 }, { "StaffID": 439, "StatID": 6, "Val": 66 }, { "StaffID": 439, "StatID": 7, "Val": 74 }, { "StaffID": 439, "StatID": 8, "Val": 68 }, { "StaffID": 439, "StatID": 9, "Val": 71 }, { "StaffID": 439, "StatID": 10, "Val": 62 }, { "StaffID": 650, "StatID": 2, "Val": 71 }, { "StaffID": 650, "StatID": 3, "Val": 69 }, { "StaffID": 650, "StatID": 4, "Val": 64 }, { "StaffID": 650, "StatID": 5, "Val": 64 }, { "StaffID": 650, "StatID": 6, "Val": 67 }, { "StaffID": 650, "StatID": 7, "Val": 72 }, { "StaffID": 650, "StatID": 8, "Val": 67 }, { "StaffID": 650, "StatID": 9, "Val": 70 }, { "StaffID": 650, "StatID": 10, "Val": 63 }, { "StaffID": 647, "StatID": 2, "Val": 63 }, { "StaffID": 647, "StatID": 3, "Val": 67 }, { "StaffID": 647, "StatID": 4, "Val": 68 }, { "StaffID": 647, "StatID": 5, "Val": 73 }, { "StaffID": 647, "StatID": 6, "Val": 74 }, { "StaffID": 647, "StatID": 7, "Val": 64 }, { "StaffID": 647, "StatID": 8, "Val": 66 }, { "StaffID": 647, "StatID": 9, "Val": 66 }, { "StaffID": 647, "StatID": 10, "Val": 68 }, { "StaffID": 380, "StatID": 2, "Val": 70 }, { "StaffID": 380, "StatID": 3, "Val": 69 }, { "StaffID": 380, "StatID": 4, "Val": 64 }, { "StaffID": 380, "StatID": 5, "Val": 64 }, { "StaffID": 380, "StatID": 6, "Val": 67 }, { "StaffID": 380, "StatID": 7, "Val": 74 }, { "StaffID": 380, "StatID": 8, "Val": 67 }, { "StaffID": 380, "StatID": 9, "Val": 69 }, { "StaffID": 380, "StatID": 10, "Val": 65 }, { "StaffID": 614, "StatID": 2, "Val": 70 }, { "StaffID": 614, "StatID": 3, "Val": 67 }, { "StaffID": 614, "StatID": 4, "Val": 65 }, { "StaffID": 614, "StatID": 5, "Val": 63 }, { "StaffID": 614, "StatID": 6, "Val": 67 }, { "StaffID": 614, "StatID": 7, "Val": 72 }, { "StaffID": 614, "StatID": 8, "Val": 67 }, { "StaffID": 614, "StatID": 9, "Val": 68 }, { "StaffID": 614, "StatID": 10, "Val": 63 }, { "StaffID": 384, "StatID": 2, "Val": 69 }, { "StaffID": 384, "StatID": 3, "Val": 67 }, { "StaffID": 384, "StatID": 4, "Val": 65 }, { "StaffID": 384, "StatID": 5, "Val": 65 }, { "StaffID": 384, "StatID": 6, "Val": 66 }, { "StaffID": 384, "StatID": 7, "Val": 70 }, { "StaffID": 384, "StatID": 8, "Val": 66 }, { "StaffID": 384, "StatID": 9, "Val": 67 }, { "StaffID": 384, "StatID": 10, "Val": 64 }, { "StaffID": 651, "StatID": 2, "Val": 68 }, { "StaffID": 651, "StatID": 3, "Val": 65 }, { "StaffID": 651, "StatID": 4, "Val": 66 }, { "StaffID": 651, "StatID": 5, "Val": 65 }, { "StaffID": 651, "StatID": 6, "Val": 66 }, { "StaffID": 651, "StatID": 7, "Val": 71 }, { "StaffID": 651, "StatID": 8, "Val": 66 }, { "StaffID": 651, "StatID": 9, "Val": 69 }, { "StaffID": 651, "StatID": 10, "Val": 65 }, { "StaffID": 615, "StatID": 2, "Val": 69 }, { "StaffID": 615, "StatID": 3, "Val": 68 }, { "StaffID": 615, "StatID": 4, "Val": 65 }, { "StaffID": 615, "StatID": 5, "Val": 65 }, { "StaffID": 615, "StatID": 6, "Val": 67 }, { "StaffID": 615, "StatID": 7, "Val": 70 }, { "StaffID": 615, "StatID": 8, "Val": 66 }, { "StaffID": 615, "StatID": 9, "Val": 68 }, { "StaffID": 615, "StatID": 10, "Val": 65 }, { "StaffID": 656, "StatID": 2, "Val": 68 }, { "StaffID": 656, "StatID": 3, "Val": 67 }, { "StaffID": 656, "StatID": 4, "Val": 64 }, { "StaffID": 656, "StatID": 5, "Val": 63 }, { "StaffID": 656, "StatID": 6, "Val": 66 }, { "StaffID": 656, "StatID": 7, "Val": 70 }, { "StaffID": 656, "StatID": 8, "Val": 65 }, { "StaffID": 656, "StatID": 9, "Val": 68 }, { "StaffID": 656, "StatID": 10, "Val": 65 }, { "StaffID": 383, "StatID": 2, "Val": 68 }, { "StaffID": 383, "StatID": 3, "Val": 66 }, { "StaffID": 383, "StatID": 4, "Val": 64 }, { "StaffID": 383, "StatID": 5, "Val": 64 }, { "StaffID": 383, "StatID": 6, "Val": 65 }, { "StaffID": 383, "StatID": 7, "Val": 71 }, { "StaffID": 383, "StatID": 8, "Val": 66 }, { "StaffID": 383, "StatID": 9, "Val": 68 }, { "StaffID": 383, "StatID": 10, "Val": 64 }, { "StaffID": 394, "StatID": 2, "Val": 67 }, { "StaffID": 394, "StatID": 3, "Val": 68 }, { "StaffID": 394, "StatID": 4, "Val": 66 }, { "StaffID": 394, "StatID": 5, "Val": 65 }, { "StaffID": 394, "StatID": 6, "Val": 66 }, { "StaffID": 394, "StatID": 7, "Val": 71 }, { "StaffID": 394, "StatID": 8, "Val": 66 }, { "StaffID": 394, "StatID": 9, "Val": 68 }, { "StaffID": 394, "StatID": 10, "Val": 66 }, { "StaffID": 667, "StatID": 2, "Val": 66 }, { "StaffID": 667, "StatID": 3, "Val": 66 }, { "StaffID": 667, "StatID": 4, "Val": 64 }, { "StaffID": 667, "StatID": 5, "Val": 68 }, { "StaffID": 667, "StatID": 6, "Val": 76 }, { "StaffID": 667, "StatID": 7, "Val": 64 }, { "StaffID": 667, "StatID": 8, "Val": 66 }, { "StaffID": 667, "StatID": 9, "Val": 72 }, { "StaffID": 667, "StatID": 10, "Val": 63 }, { "StaffID": 618, "StatID": 2, "Val": 68 }, { "StaffID": 618, "StatID": 3, "Val": 68 }, { "StaffID": 618, "StatID": 4, "Val": 64 }, { "StaffID": 618, "StatID": 5, "Val": 62 }, { "StaffID": 618, "StatID": 6, "Val": 63 }, { "StaffID": 618, "StatID": 7, "Val": 70 }, { "StaffID": 618, "StatID": 8, "Val": 63 }, { "StaffID": 618, "StatID": 9, "Val": 68 }, { "StaffID": 618, "StatID": 10, "Val": 64 }, { "StaffID": 382, "StatID": 2, "Val": 68 }, { "StaffID": 382, "StatID": 3, "Val": 66 }, { "StaffID": 382, "StatID": 4, "Val": 65 }, { "StaffID": 382, "StatID": 5, "Val": 62 }, { "StaffID": 382, "StatID": 6, "Val": 64 }, { "StaffID": 382, "StatID": 7, "Val": 66 }, { "StaffID": 382, "StatID": 8, "Val": 63 }, { "StaffID": 382, "StatID": 9, "Val": 65 }, { "StaffID": 382, "StatID": 10, "Val": 64 }, { "StaffID": 653, "StatID": 2, "Val": 66 }, { "StaffID": 653, "StatID": 3, "Val": 68 }, { "StaffID": 653, "StatID": 4, "Val": 64 }, { "StaffID": 653, "StatID": 5, "Val": 61 }, { "StaffID": 653, "StatID": 6, "Val": 63 }, { "StaffID": 653, "StatID": 7, "Val": 65 }, { "StaffID": 653, "StatID": 8, "Val": 63 }, { "StaffID": 653, "StatID": 9, "Val": 64 }, { "StaffID": 653, "StatID": 10, "Val": 63 }, { "StaffID": 654, "StatID": 2, "Val": 68 }, { "StaffID": 654, "StatID": 3, "Val": 66 }, { "StaffID": 654, "StatID": 4, "Val": 64 }, { "StaffID": 654, "StatID": 5, "Val": 62 }, { "StaffID": 654, "StatID": 6, "Val": 63 }, { "StaffID": 654, "StatID": 7, "Val": 64 }, { "StaffID": 654, "StatID": 8, "Val": 62 }, { "StaffID": 654, "StatID": 9, "Val": 65 }, { "StaffID": 654, "StatID": 10, "Val": 62 }, { "StaffID": 381, "StatID": 2, "Val": 69 }, { "StaffID": 381, "StatID": 3, "Val": 68 }, { "StaffID": 381, "StatID": 4, "Val": 64 }, { "StaffID": 381, "StatID": 5, "Val": 62 }, { "StaffID": 381, "StatID": 6, "Val": 63 }, { "StaffID": 381, "StatID": 7, "Val": 69 }, { "StaffID": 381, "StatID": 8, "Val": 63 }, { "StaffID": 381, "StatID": 9, "Val": 66 }, { "StaffID": 381, "StatID": 10, "Val": 63 }, { "StaffID": 617, "StatID": 2, "Val": 68 }, { "StaffID": 617, "StatID": 3, "Val": 65 }, { "StaffID": 617, "StatID": 4, "Val": 63 }, { "StaffID": 617, "StatID": 5, "Val": 64 }, { "StaffID": 617, "StatID": 6, "Val": 62 }, { "StaffID": 617, "StatID": 7, "Val": 64 }, { "StaffID": 617, "StatID": 8, "Val": 61 }, { "StaffID": 617, "StatID": 9, "Val": 64 }, { "StaffID": 617, "StatID": 10, "Val": 62 }, { "StaffID": 657, "StatID": 2, "Val": 70 }, { "StaffID": 657, "StatID": 3, "Val": 65 }, { "StaffID": 657, "StatID": 4, "Val": 62 }, { "StaffID": 657, "StatID": 5, "Val": 60 }, { "StaffID": 657, "StatID": 6, "Val": 62 }, { "StaffID": 657, "StatID": 7, "Val": 68 }, { "StaffID": 657, "StatID": 8, "Val": 63 }, { "StaffID": 657, "StatID": 9, "Val": 66 }, { "StaffID": 657, "StatID": 10, "Val": 60 }, { "StaffID": 123, "StatID": 2, "Val": 64 }, { "StaffID": 123, "StatID": 3, "Val": 66 }, { "StaffID": 123, "StatID": 4, "Val": 64 }, { "StaffID": 123, "StatID": 5, "Val": 66 }, { "StaffID": 123, "StatID": 6, "Val": 64 }, { "StaffID": 123, "StatID": 7, "Val": 60 }, { "StaffID": 123, "StatID": 8, "Val": 61 }, { "StaffID": 123, "StatID": 9, "Val": 61 }, { "StaffID": 123, "StatID": 10, "Val": 65 }, { "StaffID": 253, "StatID": 2, "Val": 68 }, { "StaffID": 253, "StatID": 3, "Val": 64 }, { "StaffID": 253, "StatID": 4, "Val": 64 }, { "StaffID": 253, "StatID": 5, "Val": 64 }, { "StaffID": 253, "StatID": 6, "Val": 63 }, { "StaffID": 253, "StatID": 7, "Val": 60 }, { "StaffID": 253, "StatID": 8, "Val": 62 }, { "StaffID": 253, "StatID": 9, "Val": 62 }, { "StaffID": 253, "StatID": 10, "Val": 62 }, { "StaffID": 611, "StatID": 2, "Val": 67 }, { "StaffID": 611, "StatID": 3, "Val": 65 }, { "StaffID": 611, "StatID": 4, "Val": 62 }, { "StaffID": 611, "StatID": 5, "Val": 61 }, { "StaffID": 611, "StatID": 6, "Val": 63 }, { "StaffID": 611, "StatID": 7, "Val": 67 }, { "StaffID": 611, "StatID": 8, "Val": 62 }, { "StaffID": 611, "StatID": 9, "Val": 64 }, { "StaffID": 611, "StatID": 10, "Val": 61 }, { "StaffID": 648, "StatID": 2, "Val": 66 }, { "StaffID": 648, "StatID": 3, "Val": 64 }, { "StaffID": 648, "StatID": 4, "Val": 63 }, { "StaffID": 648, "StatID": 5, "Val": 65 }, { "StaffID": 648, "StatID": 6, "Val": 63 }, { "StaffID": 648, "StatID": 7, "Val": 62 }, { "StaffID": 648, "StatID": 8, "Val": 62 }, { "StaffID": 648, "StatID": 9, "Val": 62 }, { "StaffID": 648, "StatID": 10, "Val": 63 }, { "StaffID": 652, "StatID": 2, "Val": 67 }, { "StaffID": 652, "StatID": 3, "Val": 67 }, { "StaffID": 652, "StatID": 4, "Val": 63 }, { "StaffID": 652, "StatID": 5, "Val": 64 }, { "StaffID": 652, "StatID": 6, "Val": 65 }, { "StaffID": 652, "StatID": 7, "Val": 62 }, { "StaffID": 652, "StatID": 8, "Val": 63 }, { "StaffID": 652, "StatID": 9, "Val": 64 }, { "StaffID": 652, "StatID": 10, "Val": 64 }, { "StaffID": 602, "StatID": 2, "Val": 66 }, { "StaffID": 602, "StatID": 3, "Val": 66 }, { "StaffID": 602, "StatID": 4, "Val": 63 }, { "StaffID": 602, "StatID": 5, "Val": 63 }, { "StaffID": 602, "StatID": 6, "Val": 62 }, { "StaffID": 602, "StatID": 7, "Val": 64 }, { "StaffID": 602, "StatID": 8, "Val": 62 }, { "StaffID": 602, "StatID": 9, "Val": 62 }, { "StaffID": 602, "StatID": 10, "Val": 62 }, { "StaffID": 655, "StatID": 2, "Val": 67 }, { "StaffID": 655, "StatID": 3, "Val": 64 }, { "StaffID": 655, "StatID": 4, "Val": 63 }, { "StaffID": 655, "StatID": 5, "Val": 61 }, { "StaffID": 655, "StatID": 6, "Val": 63 }, { "StaffID": 655, "StatID": 7, "Val": 64 }, { "StaffID": 655, "StatID": 8, "Val": 62 }, { "StaffID": 655, "StatID": 9, "Val": 63 }, { "StaffID": 655, "StatID": 10, "Val": 60 }, { "StaffID": 387, "StatID": 2, "Val": 65 }, { "StaffID": 387, "StatID": 3, "Val": 66 }, { "StaffID": 387, "StatID": 4, "Val": 63 }, { "StaffID": 387, "StatID": 5, "Val": 61 }, { "StaffID": 387, "StatID": 6, "Val": 60 }, { "StaffID": 387, "StatID": 7, "Val": 65 }, { "StaffID": 387, "StatID": 8, "Val": 61 }, { "StaffID": 387, "StatID": 9, "Val": 63 }, { "StaffID": 387, "StatID": 10, "Val": 63 }, { "StaffID": 407, "StatID": 2, "Val": 68 }, { "StaffID": 407, "StatID": 3, "Val": 64 }, { "StaffID": 407, "StatID": 4, "Val": 61 }, { "StaffID": 407, "StatID": 5, "Val": 57 }, { "StaffID": 407, "StatID": 6, "Val": 62 }, { "StaffID": 407, "StatID": 7, "Val": 69 }, { "StaffID": 407, "StatID": 8, "Val": 61 }, { "StaffID": 407, "StatID": 9, "Val": 68 }, { "StaffID": 407, "StatID": 10, "Val": 59 }, { "StaffID": 660, "StatID": 2, "Val": 67 }, { "StaffID": 660, "StatID": 3, "Val": 64 }, { "StaffID": 660, "StatID": 4, "Val": 62 }, { "StaffID": 660, "StatID": 5, "Val": 60 }, { "StaffID": 660, "StatID": 6, "Val": 60 }, { "StaffID": 660, "StatID": 7, "Val": 63 }, { "StaffID": 660, "StatID": 8, "Val": 61 }, { "StaffID": 660, "StatID": 9, "Val": 63 }, { "StaffID": 660, "StatID": 10, "Val": 60 }, { "StaffID": 661, "StatID": 2, "Val": 64 }, { "StaffID": 661, "StatID": 3, "Val": 62 }, { "StaffID": 661, "StatID": 4, "Val": 64 }, { "StaffID": 661, "StatID": 5, "Val": 63 }, { "StaffID": 661, "StatID": 6, "Val": 61 }, { "StaffID": 661, "StatID": 7, "Val": 62 }, { "StaffID": 661, "StatID": 8, "Val": 61 }, { "StaffID": 661, "StatID": 9, "Val": 63 }, { "StaffID": 661, "StatID": 10, "Val": 64 }, { "StaffID": 389, "StatID": 2, "Val": 64 }, { "StaffID": 389, "StatID": 3, "Val": 65 }, { "StaffID": 389, "StatID": 4, "Val": 62 }, { "StaffID": 389, "StatID": 5, "Val": 61 }, { "StaffID": 389, "StatID": 6, "Val": 63 }, { "StaffID": 389, "StatID": 7, "Val": 61 }, { "StaffID": 389, "StatID": 8, "Val": 61 }, { "StaffID": 389, "StatID": 9, "Val": 62 }, { "StaffID": 389, "StatID": 10, "Val": 64 }, { "StaffID": 621, "StatID": 2, "Val": 66 }, { "StaffID": 621, "StatID": 3, "Val": 63 }, { "StaffID": 621, "StatID": 4, "Val": 62 }, { "StaffID": 621, "StatID": 5, "Val": 60 }, { "StaffID": 621, "StatID": 6, "Val": 60 }, { "StaffID": 621, "StatID": 7, "Val": 62 }, { "StaffID": 621, "StatID": 8, "Val": 61 }, { "StaffID": 621, "StatID": 9, "Val": 62 }, { "StaffID": 621, "StatID": 10, "Val": 60 }, { "StaffID": 659, "StatID": 2, "Val": 63 }, { "StaffID": 659, "StatID": 3, "Val": 61 }, { "StaffID": 659, "StatID": 4, "Val": 64 }, { "StaffID": 659, "StatID": 5, "Val": 62 }, { "StaffID": 659, "StatID": 6, "Val": 61 }, { "StaffID": 659, "StatID": 7, "Val": 62 }, { "StaffID": 659, "StatID": 8, "Val": 61 }, { "StaffID": 659, "StatID": 9, "Val": 62 }, { "StaffID": 659, "StatID": 10, "Val": 64 }, { "StaffID": 616, "StatID": 2, "Val": 65 }, { "StaffID": 616, "StatID": 3, "Val": 63 }, { "StaffID": 616, "StatID": 4, "Val": 62 }, { "StaffID": 616, "StatID": 5, "Val": 61 }, { "StaffID": 616, "StatID": 6, "Val": 60 }, { "StaffID": 616, "StatID": 7, "Val": 60 }, { "StaffID": 616, "StatID": 8, "Val": 61 }, { "StaffID": 616, "StatID": 9, "Val": 61 }, { "StaffID": 616, "StatID": 10, "Val": 60 }, { "StaffID": 613, "StatID": 2, "Val": 65 }, { "StaffID": 613, "StatID": 3, "Val": 66 }, { "StaffID": 613, "StatID": 4, "Val": 62 }, { "StaffID": 613, "StatID": 5, "Val": 61 }, { "StaffID": 613, "StatID": 6, "Val": 59 }, { "StaffID": 613, "StatID": 7, "Val": 62 }, { "StaffID": 613, "StatID": 8, "Val": 61 }, { "StaffID": 613, "StatID": 9, "Val": 61 }, { "StaffID": 613, "StatID": 10, "Val": 63 }, { "StaffID": 646, "StatID": 2, "Val": 63 }, { "StaffID": 646, "StatID": 3, "Val": 64 }, { "StaffID": 646, "StatID": 4, "Val": 61 }, { "StaffID": 646, "StatID": 5, "Val": 60 }, { "StaffID": 646, "StatID": 6, "Val": 61 }, { "StaffID": 646, "StatID": 7, "Val": 60 }, { "StaffID": 646, "StatID": 8, "Val": 60 }, { "StaffID": 646, "StatID": 9, "Val": 61 }, { "StaffID": 646, "StatID": 10, "Val": 62 }, { "StaffID": 662, "StatID": 2, "Val": 64 }, { "StaffID": 662, "StatID": 3, "Val": 62 }, { "StaffID": 662, "StatID": 4, "Val": 61 }, { "StaffID": 662, "StatID": 5, "Val": 59 }, { "StaffID": 662, "StatID": 6, "Val": 61 }, { "StaffID": 662, "StatID": 7, "Val": 59 }, { "StaffID": 662, "StatID": 8, "Val": 60 }, { "StaffID": 662, "StatID": 9, "Val": 61 }, { "StaffID": 662, "StatID": 10, "Val": 59 }, { "StaffID": 619, "StatID": 2, "Val": 64 }, { "StaffID": 619, "StatID": 3, "Val": 62 }, { "StaffID": 619, "StatID": 4, "Val": 61 }, { "StaffID": 619, "StatID": 5, "Val": 59 }, { "StaffID": 619, "StatID": 6, "Val": 60 }, { "StaffID": 619, "StatID": 7, "Val": 59 }, { "StaffID": 619, "StatID": 8, "Val": 60 }, { "StaffID": 619, "StatID": 9, "Val": 61 }, { "StaffID": 619, "StatID": 10, "Val": 62 }, { "StaffID": 405, "StatID": 2, "Val": 64 }, { "StaffID": 405, "StatID": 3, "Val": 62 }, { "StaffID": 405, "StatID": 4, "Val": 54 }, { "StaffID": 405, "StatID": 5, "Val": 54 }, { "StaffID": 405, "StatID": 6, "Val": 54 }, { "StaffID": 405, "StatID": 7, "Val": 62 }, { "StaffID": 405, "StatID": 8, "Val": 54 }, { "StaffID": 405, "StatID": 9, "Val": 60 }, { "StaffID": 405, "StatID": 10, "Val": 54 }, { "StaffID": 620, "StatID": 2, "Val": 62 }, { "StaffID": 620, "StatID": 3, "Val": 62 }, { "StaffID": 620, "StatID": 4, "Val": 62 }, { "StaffID": 620, "StatID": 5, "Val": 62 }, { "StaffID": 620, "StatID": 6, "Val": 55 }, { "StaffID": 620, "StatID": 7, "Val": 58 }, { "StaffID": 620, "StatID": 8, "Val": 55 }, { "StaffID": 620, "StatID": 9, "Val": 61 }, { "StaffID": 620, "StatID": 10, "Val": 62 }, { "StaffID": 605, "StatID": 2, "Val": 54 }, { "StaffID": 605, "StatID": 3, "Val": 57 }, { "StaffID": 605, "StatID": 4, "Val": 61 }, { "StaffID": 605, "StatID": 5, "Val": 61 }, { "StaffID": 605, "StatID": 6, "Val": 61 }, { "StaffID": 605, "StatID": 7, "Val": 54 }, { "StaffID": 605, "StatID": 8, "Val": 54 }, { "StaffID": 605, "StatID": 9, "Val": 54 }, { "StaffID": 605, "StatID": 10, "Val": 63 }, { "StaffID": 612, "StatID": 2, "Val": 62 }, { "StaffID": 612, "StatID": 3, "Val": 62 }, { "StaffID": 612, "StatID": 4, "Val": 60 }, { "StaffID": 612, "StatID": 5, "Val": 61 }, { "StaffID": 612, "StatID": 6, "Val": 55 }, { "StaffID": 612, "StatID": 7, "Val": 61 }, { "StaffID": 612, "StatID": 8, "Val": 59 }, { "StaffID": 612, "StatID": 9, "Val": 61 }, { "StaffID": 612, "StatID": 10, "Val": 59 }, { "StaffID": 601, "StatID": 2, "Val": 61 }, { "StaffID": 601, "StatID": 3, "Val": 61 }, { "StaffID": 601, "StatID": 4, "Val": 61 }, { "StaffID": 601, "StatID": 5, "Val": 61 }, { "StaffID": 601, "StatID": 6, "Val": 54 }, { "StaffID": 601, "StatID": 7, "Val": 57 }, { "StaffID": 601, "StatID": 8, "Val": 54 }, { "StaffID": 601, "StatID": 9, "Val": 60 }, { "StaffID": 601, "StatID": 10, "Val": 61 }, { "StaffID": 385, "StatID": 2, "Val": 61 }, { "StaffID": 385, "StatID": 3, "Val": 61 }, { "StaffID": 385, "StatID": 4, "Val": 59 }, { "StaffID": 385, "StatID": 5, "Val": 60 }, { "StaffID": 385, "StatID": 6, "Val": 54 }, { "StaffID": 385, "StatID": 7, "Val": 61 }, { "StaffID": 385, "StatID": 8, "Val": 54 }, { "StaffID": 385, "StatID": 9, "Val": 60 }, { "StaffID": 385, "StatID": 10, "Val": 59 }, { "StaffID": 658, "StatID": 2, "Val": 59 }, { "StaffID": 658, "StatID": 3, "Val": 60 }, { "StaffID": 658, "StatID": 4, "Val": 60 }, { "StaffID": 658, "StatID": 5, "Val": 60 }, { "StaffID": 658, "StatID": 6, "Val": 53 }, { "StaffID": 658, "StatID": 7, "Val": 58 }, { "StaffID": 658, "StatID": 8, "Val": 56 }, { "StaffID": 658, "StatID": 9, "Val": 58 }, { "StaffID": 658, "StatID": 10, "Val": 59 }, { "StaffID": 418, "StatID": 2, "Val": 54 }, { "StaffID": 418, "StatID": 3, "Val": 54 }, { "StaffID": 418, "StatID": 4, "Val": 60 }, { "StaffID": 418, "StatID": 5, "Val": 60 }, { "StaffID": 418, "StatID": 6, "Val": 60 }, { "StaffID": 418, "StatID": 7, "Val": 54 }, { "StaffID": 418, "StatID": 8, "Val": 55 }, { "StaffID": 418, "StatID": 9, "Val": 58 }, { "StaffID": 418, "StatID": 10, "Val": 61 }, { "StaffID": 402, "StatID": 2, "Val": 63 }, { "StaffID": 402, "StatID": 3, "Val": 61 }, { "StaffID": 402, "StatID": 4, "Val": 54 }, { "StaffID": 402, "StatID": 5, "Val": 54 }, { "StaffID": 402, "StatID": 6, "Val": 54 }, { "StaffID": 402, "StatID": 7, "Val": 61 }, { "StaffID": 402, "StatID": 8, "Val": 54 }, { "StaffID": 402, "StatID": 9, "Val": 57 }, { "StaffID": 402, "StatID": 10, "Val": 54 }, { "StaffID": 406, "StatID": 2, "Val": 58 }, { "StaffID": 406, "StatID": 3, "Val": 62 }, { "StaffID": 406, "StatID": 4, "Val": 58 }, { "StaffID": 406, "StatID": 5, "Val": 59 }, { "StaffID": 406, "StatID": 6, "Val": 56 }, { "StaffID": 406, "StatID": 7, "Val": 58 }, { "StaffID": 406, "StatID": 8, "Val": 58 }, { "StaffID": 406, "StatID": 9, "Val": 56 }, { "StaffID": 406, "StatID": 10, "Val": 57 }, { "StaffID": 664, "StatID": 2, "Val": 59 }, { "StaffID": 664, "StatID": 3, "Val": 57 }, { "StaffID": 664, "StatID": 4, "Val": 58 }, { "StaffID": 664, "StatID": 5, "Val": 58 }, { "StaffID": 664, "StatID": 6, "Val": 56 }, { "StaffID": 664, "StatID": 7, "Val": 57 }, { "StaffID": 664, "StatID": 8, "Val": 58 }, { "StaffID": 664, "StatID": 9, "Val": 56 }, { "StaffID": 664, "StatID": 10, "Val": 55 }, { "StaffID": 663, "StatID": 2, "Val": 61 }, { "StaffID": 663, "StatID": 3, "Val": 59 }, { "StaffID": 663, "StatID": 4, "Val": 59 }, { "StaffID": 663, "StatID": 5, "Val": 51 }, { "StaffID": 663, "StatID": 6, "Val": 52 }, { "StaffID": 663, "StatID": 7, "Val": 52 }, { "StaffID": 663, "StatID": 8, "Val": 52 }, { "StaffID": 663, "StatID": 9, "Val": 58 }, { "StaffID": 663, "StatID": 10, "Val": 59 }, { "StaffID": 600, "StatID": 2, "Val": 52 }, { "StaffID": 600, "StatID": 3, "Val": 53 }, { "StaffID": 600, "StatID": 4, "Val": 58 }, { "StaffID": 600, "StatID": 5, "Val": 61 }, { "StaffID": 600, "StatID": 6, "Val": 60 }, { "StaffID": 600, "StatID": 7, "Val": 52 }, { "StaffID": 600, "StatID": 8, "Val": 52 }, { "StaffID": 600, "StatID": 9, "Val": 56 }, { "StaffID": 600, "StatID": 10, "Val": 57 }, { "StaffID": 674, "StatID": 2, "Val": 58 }, { "StaffID": 674, "StatID": 3, "Val": 57 }, { "StaffID": 674, "StatID": 4, "Val": 52 }, { "StaffID": 674, "StatID": 5, "Val": 52 }, { "StaffID": 674, "StatID": 6, "Val": 57 }, { "StaffID": 674, "StatID": 7, "Val": 61 }, { "StaffID": 674, "StatID": 8, "Val": 58 }, { "StaffID": 674, "StatID": 9, "Val": 58 }, { "StaffID": 674, "StatID": 10, "Val": 52 }, { "StaffID": 606, "StatID": 2, "Val": 56 }, { "StaffID": 606, "StatID": 3, "Val": 56 }, { "StaffID": 606, "StatID": 4, "Val": 56 }, { "StaffID": 606, "StatID": 5, "Val": 56 }, { "StaffID": 606, "StatID": 6, "Val": 50 }, { "StaffID": 606, "StatID": 7, "Val": 52 }, { "StaffID": 606, "StatID": 8, "Val": 50 }, { "StaffID": 606, "StatID": 9, "Val": 56 }, { "StaffID": 606, "StatID": 10, "Val": 55 }, { "StaffID": 671, "StatID": 2, "Val": 58 }, { "StaffID": 671, "StatID": 3, "Val": 56 }, { "StaffID": 671, "StatID": 4, "Val": 52 }, { "StaffID": 671, "StatID": 5, "Val": 53 }, { "StaffID": 671, "StatID": 6, "Val": 48 }, { "StaffID": 671, "StatID": 7, "Val": 57 }, { "StaffID": 671, "StatID": 8, "Val": 49 }, { "StaffID": 671, "StatID": 9, "Val": 58 }, { "StaffID": 671, "StatID": 10, "Val": 51 }, { "StaffID": 409, "StatID": 2, "Val": 53 }, { "StaffID": 409, "StatID": 3, "Val": 53 }, { "StaffID": 409, "StatID": 4, "Val": 52 }, { "StaffID": 409, "StatID": 5, "Val": 52 }, { "StaffID": 409, "StatID": 6, "Val": 48 }, { "StaffID": 409, "StatID": 7, "Val": 48 }, { "StaffID": 409, "StatID": 8, "Val": 48 }, { "StaffID": 409, "StatID": 9, "Val": 52 }, { "StaffID": 409, "StatID": 10, "Val": 52 }, { "StaffID": 609, "StatID": 2, "Val": 52 }, { "StaffID": 609, "StatID": 3, "Val": 55 }, { "StaffID": 609, "StatID": 4, "Val": 53 }, { "StaffID": 609, "StatID": 5, "Val": 54 }, { "StaffID": 609, "StatID": 6, "Val": 48 }, { "StaffID": 609, "StatID": 7, "Val": 53 }, { "StaffID": 609, "StatID": 8, "Val": 48 }, { "StaffID": 609, "StatID": 9, "Val": 52 }, { "StaffID": 609, "StatID": 10, "Val": 53 }, { "StaffID": 672, "StatID": 2, "Val": 57 }, { "StaffID": 672, "StatID": 3, "Val": 55 }, { "StaffID": 672, "StatID": 4, "Val": 55 }, { "StaffID": 672, "StatID": 5, "Val": 54 }, { "StaffID": 672, "StatID": 6, "Val": 49 }, { "StaffID": 672, "StatID": 7, "Val": 54 }, { "StaffID": 672, "StatID": 8, "Val": 49 }, { "StaffID": 672, "StatID": 9, "Val": 55 }, { "StaffID": 672, "StatID": 10, "Val": 54 }, { "StaffID": 410, "StatID": 2, "Val": 54 }, { "StaffID": 410, "StatID": 3, "Val": 52 }, { "StaffID": 410, "StatID": 4, "Val": 51 }, { "StaffID": 410, "StatID": 5, "Val": 52 }, { "StaffID": 410, "StatID": 6, "Val": 52 }, { "StaffID": 410, "StatID": 7, "Val": 54 }, { "StaffID": 410, "StatID": 8, "Val": 52 }, { "StaffID": 410, "StatID": 9, "Val": 53 }, { "StaffID": 410, "StatID": 10, "Val": 51 }, { "StaffID": 673, "StatID": 2, "Val": 53 }, { "StaffID": 673, "StatID": 3, "Val": 53 }, { "StaffID": 673, "StatID": 4, "Val": 54 }, { "StaffID": 673, "StatID": 5, "Val": 54 }, { "StaffID": 673, "StatID": 6, "Val": 48 }, { "StaffID": 673, "StatID": 7, "Val": 53 }, { "StaffID": 673, "StatID": 8, "Val": 48 }, { "StaffID": 673, "StatID": 9, "Val": 53 }, { "StaffID": 673, "StatID": 10, "Val": 54 }, { "StaffID": 419, "StatID": 2, "Val": 52 }, { "StaffID": 419, "StatID": 3, "Val": 52 }, { "StaffID": 419, "StatID": 4, "Val": 53 }, { "StaffID": 419, "StatID": 5, "Val": 52 }, { "StaffID": 419, "StatID": 6, "Val": 48 }, { "StaffID": 419, "StatID": 7, "Val": 49 }, { "StaffID": 419, "StatID": 8, "Val": 48 }, { "StaffID": 419, "StatID": 9, "Val": 52 }, { "StaffID": 419, "StatID": 10, "Val": 53 }, { "StaffID": 665, "StatID": 2, "Val": 51 }, { "StaffID": 665, "StatID": 3, "Val": 53 }, { "StaffID": 665, "StatID": 4, "Val": 52 }, { "StaffID": 665, "StatID": 5, "Val": 52 }, { "StaffID": 665, "StatID": 6, "Val": 48 }, { "StaffID": 665, "StatID": 7, "Val": 48 }, { "StaffID": 665, "StatID": 8, "Val": 48 }, { "StaffID": 665, "StatID": 9, "Val": 52 }, { "StaffID": 665, "StatID": 10, "Val": 52 }, { "StaffID": 668, "StatID": 2, "Val": 53 }, { "StaffID": 668, "StatID": 3, "Val": 51 }, { "StaffID": 668, "StatID": 4, "Val": 50 }, { "StaffID": 668, "StatID": 5, "Val": 51 }, { "StaffID": 668, "StatID": 6, "Val": 48 }, { "StaffID": 668, "StatID": 7, "Val": 50 }, { "StaffID": 668, "StatID": 8, "Val": 50 }, { "StaffID": 668, "StatID": 9, "Val": 51 }, { "StaffID": 668, "StatID": 10, "Val": 50 }, { "StaffID": 669, "StatID": 2, "Val": 52 }, { "StaffID": 669, "StatID": 3, "Val": 49 }, { "StaffID": 669, "StatID": 4, "Val": 49 }, { "StaffID": 669, "StatID": 5, "Val": 51 }, { "StaffID": 669, "StatID": 6, "Val": 48 }, { "StaffID": 669, "StatID": 7, "Val": 50 }, { "StaffID": 669, "StatID": 8, "Val": 50 }, { "StaffID": 669, "StatID": 9, "Val": 50 }, { "StaffID": 669, "StatID": 10, "Val": 50 }, { "StaffID": 607, "StatID": 2, "Val": 50 }, { "StaffID": 607, "StatID": 3, "Val": 52 }, { "StaffID": 607, "StatID": 4, "Val": 50 }, { "StaffID": 607, "StatID": 5, "Val": 51 }, { "StaffID": 607, "StatID": 6, "Val": 48 }, { "StaffID": 607, "StatID": 7, "Val": 50 }, { "StaffID": 607, "StatID": 8, "Val": 48 }, { "StaffID": 607, "StatID": 9, "Val": 49 }, { "StaffID": 607, "StatID": 10, "Val": 50 }, { "StaffID": 670, "StatID": 2, "Val": 54 }, { "StaffID": 670, "StatID": 3, "Val": 52 }, { "StaffID": 670, "StatID": 4, "Val": 50 }, { "StaffID": 670, "StatID": 5, "Val": 49 }, { "StaffID": 670, "StatID": 6, "Val": 48 }, { "StaffID": 670, "StatID": 7, "Val": 50 }, { "StaffID": 670, "StatID": 8, "Val": 48 }, { "StaffID": 670, "StatID": 9, "Val": 52 }, { "StaffID": 670, "StatID": 10, "Val": 49 }, { "StaffID": 666, "StatID": 2, "Val": 52 }, { "StaffID": 666, "StatID": 3, "Val": 50 }, { "StaffID": 666, "StatID": 4, "Val": 49 }, { "StaffID": 666, "StatID": 5, "Val": 50 }, { "StaffID": 666, "StatID": 6, "Val": 48 }, { "StaffID": 666, "StatID": 7, "Val": 49 }, { "StaffID": 666, "StatID": 8, "Val": 48 }, { "StaffID": 666, "StatID": 9, "Val": 50 }, { "StaffID": 666, "StatID": 10, "Val": 49 }, { "StaffID": 304, "StatID": 2, "Val": 67 }, { "StaffID": 304, "StatID": 3, "Val": 67 }, { "StaffID": 304, "StatID": 4, "Val": 61 }, { "StaffID": 304, "StatID": 5, "Val": 62 }, { "StaffID": 304, "StatID": 6, "Val": 62 }, { "StaffID": 304, "StatID": 7, "Val": 63 }, { "StaffID": 304, "StatID": 8, "Val": 61 }, { "StaffID": 304, "StatID": 9, "Val": 62 }, { "StaffID": 304, "StatID": 10, "Val": 62 }, { "StaffID": 305, "StatID": 2, "Val": 77 }, { "StaffID": 305, "StatID": 3, "Val": 76 }, { "StaffID": 305, "StatID": 4, "Val": 70 }, { "StaffID": 305, "StatID": 5, "Val": 70 }, { "StaffID": 305, "StatID": 6, "Val": 69 }, { "StaffID": 305, "StatID": 7, "Val": 76 }, { "StaffID": 305, "StatID": 8, "Val": 72 }, { "StaffID": 305, "StatID": 9, "Val": 77 }, { "StaffID": 305, "StatID": 10, "Val": 72 }, { "StaffID": 417, "StatID": 2, "Val": 66 }, { "StaffID": 417, "StatID": 3, "Val": 63 }, { "StaffID": 417, "StatID": 4, "Val": 58 }, { "StaffID": 417, "StatID": 5, "Val": 57 }, { "StaffID": 417, "StatID": 6, "Val": 60 }, { "StaffID": 417, "StatID": 7, "Val": 61 }, { "StaffID": 417, "StatID": 8, "Val": 56 }, { "StaffID": 417, "StatID": 9, "Val": 62 }, { "StaffID": 417, "StatID": 10, "Val": 58 }, { "StaffID": 9, "StatID": 2, "Val": 77 }, { "StaffID": 9, "StatID": 3, "Val": 74 }, { "StaffID": 9, "StatID": 4, "Val": 85 }, { "StaffID": 9, "StatID": 5, "Val": 94 }, { "StaffID": 9, "StatID": 6, "Val": 78 }, { "StaffID": 9, "StatID": 7, "Val": 82 }, { "StaffID": 9, "StatID": 8, "Val": 85 }, { "StaffID": 9, "StatID": 9, "Val": 89 }, { "StaffID": 9, "StatID": 10, "Val": 82 }, { "StaffID": 78, "StatID": 2, "Val": 67 }, { "StaffID": 78, "StatID": 3, "Val": 61 }, { "StaffID": 78, "StatID": 4, "Val": 65 }, { "StaffID": 78, "StatID": 5, "Val": 70 }, { "StaffID": 78, "StatID": 6, "Val": 70 }, { "StaffID": 78, "StatID": 7, "Val": 73 }, { "StaffID": 78, "StatID": 8, "Val": 75 }, { "StaffID": 78, "StatID": 9, "Val": 67 }, { "StaffID": 78, "StatID": 10, "Val": 74 }, { "StaffID": 91, "StatID": 2, "Val": 71 }, { "StaffID": 91, "StatID": 3, "Val": 76 }, { "StaffID": 91, "StatID": 4, "Val": 82 }, { "StaffID": 91, "StatID": 5, "Val": 72 }, { "StaffID": 91, "StatID": 6, "Val": 72 }, { "StaffID": 91, "StatID": 7, "Val": 61 }, { "StaffID": 91, "StatID": 8, "Val": 76 }, { "StaffID": 91, "StatID": 9, "Val": 78 }, { "StaffID": 91, "StatID": 10, "Val": 73 }, { "StaffID": 114, "StatID": 2, "Val": 48 }, { "StaffID": 114, "StatID": 3, "Val": 46 }, { "StaffID": 114, "StatID": 4, "Val": 62 }, { "StaffID": 114, "StatID": 5, "Val": 66 }, { "StaffID": 114, "StatID": 6, "Val": 66 }, { "StaffID": 114, "StatID": 7, "Val": 57 }, { "StaffID": 114, "StatID": 8, "Val": 63 }, { "StaffID": 114, "StatID": 9, "Val": 44 }, { "StaffID": 114, "StatID": 10, "Val": 53 }, { "StaffID": 115, "StatID": 2, "Val": 49 }, { "StaffID": 115, "StatID": 3, "Val": 59 }, { "StaffID": 115, "StatID": 4, "Val": 59 }, { "StaffID": 115, "StatID": 5, "Val": 56 }, { "StaffID": 115, "StatID": 6, "Val": 58 }, { "StaffID": 115, "StatID": 7, "Val": 53 }, { "StaffID": 115, "StatID": 8, "Val": 55 }, { "StaffID": 115, "StatID": 9, "Val": 55 }, { "StaffID": 115, "StatID": 10, "Val": 60 }, { "StaffID": 128, "StatID": 2, "Val": 69 }, { "StaffID": 128, "StatID": 3, "Val": 64 }, { "StaffID": 128, "StatID": 4, "Val": 70 }, { "StaffID": 128, "StatID": 5, "Val": 56 }, { "StaffID": 128, "StatID": 6, "Val": 63 }, { "StaffID": 128, "StatID": 7, "Val": 51 }, { "StaffID": 128, "StatID": 8, "Val": 55 }, { "StaffID": 128, "StatID": 9, "Val": 66 }, { "StaffID": 128, "StatID": 10, "Val": 62 }, { "StaffID": 133, "StatID": 2, "Val": 63 }, { "StaffID": 133, "StatID": 3, "Val": 60 }, { "StaffID": 133, "StatID": 4, "Val": 72 }, { "StaffID": 133, "StatID": 5, "Val": 75 }, { "StaffID": 133, "StatID": 6, "Val": 69 }, { "StaffID": 133, "StatID": 7, "Val": 74 }, { "StaffID": 133, "StatID": 8, "Val": 78 }, { "StaffID": 133, "StatID": 9, "Val": 68 }, { "StaffID": 133, "StatID": 10, "Val": 63 }, { "StaffID": 143, "StatID": 2, "Val": 64 }, { "StaffID": 143, "StatID": 3, "Val": 67 }, { "StaffID": 143, "StatID": 4, "Val": 60 }, { "StaffID": 143, "StatID": 5, "Val": 48 }, { "StaffID": 143, "StatID": 6, "Val": 59 }, { "StaffID": 143, "StatID": 7, "Val": 48 }, { "StaffID": 143, "StatID": 8, "Val": 57 }, { "StaffID": 143, "StatID": 9, "Val": 63 }, { "StaffID": 143, "StatID": 10, "Val": 62 }, { "StaffID": 244, "StatID": 2, "Val": 46 }, { "StaffID": 244, "StatID": 3, "Val": 45 }, { "StaffID": 244, "StatID": 4, "Val": 57 }, { "StaffID": 244, "StatID": 5, "Val": 58 }, { "StaffID": 244, "StatID": 6, "Val": 61 }, { "StaffID": 244, "StatID": 7, "Val": 47 }, { "StaffID": 244, "StatID": 8, "Val": 66 }, { "StaffID": 244, "StatID": 9, "Val": 50 }, { "StaffID": 244, "StatID": 10, "Val": 60 }, { "StaffID": 258, "StatID": 2, "Val": 50 }, { "StaffID": 258, "StatID": 3, "Val": 50 }, { "StaffID": 258, "StatID": 4, "Val": 50 }, { "StaffID": 258, "StatID": 5, "Val": 50 }, { "StaffID": 258, "StatID": 6, "Val": 50 }, { "StaffID": 258, "StatID": 7, "Val": 50 }, { "StaffID": 258, "StatID": 8, "Val": 50 }, { "StaffID": 258, "StatID": 9, "Val": 50 }, { "StaffID": 258, "StatID": 10, "Val": 50 }, { "StaffID": 259, "StatID": 2, "Val": 50 }, { "StaffID": 259, "StatID": 3, "Val": 50 }, { "StaffID": 259, "StatID": 4, "Val": 50 }, { "StaffID": 259, "StatID": 5, "Val": 50 }, { "StaffID": 259, "StatID": 6, "Val": 50 }, { "StaffID": 259, "StatID": 7, "Val": 50 }, { "StaffID": 259, "StatID": 8, "Val": 50 }, { "StaffID": 259, "StatID": 9, "Val": 50 }, { "StaffID": 259, "StatID": 10, "Val": 50 }, { "StaffID": 260, "StatID": 2, "Val": 50 }, { "StaffID": 260, "StatID": 3, "Val": 50 }, { "StaffID": 260, "StatID": 4, "Val": 50 }, { "StaffID": 260, "StatID": 5, "Val": 50 }, { "StaffID": 260, "StatID": 6, "Val": 50 }, { "StaffID": 260, "StatID": 7, "Val": 50 }, { "StaffID": 260, "StatID": 8, "Val": 50 }, { "StaffID": 260, "StatID": 9, "Val": 50 }, { "StaffID": 260, "StatID": 10, "Val": 50 }, { "StaffID": 272, "StatID": 2, "Val": 58 }, { "StaffID": 272, "StatID": 3, "Val": 57 }, { "StaffID": 272, "StatID": 4, "Val": 64 }, { "StaffID": 272, "StatID": 5, "Val": 56 }, { "StaffID": 272, "StatID": 6, "Val": 56 }, { "StaffID": 272, "StatID": 7, "Val": 53 }, { "StaffID": 272, "StatID": 8, "Val": 61 }, { "StaffID": 272, "StatID": 9, "Val": 56 }, { "StaffID": 272, "StatID": 10, "Val": 62 }, { "StaffID": 300, "StatID": 2, "Val": 49 }, { "StaffID": 300, "StatID": 3, "Val": 49 }, { "StaffID": 300, "StatID": 4, "Val": 64 }, { "StaffID": 300, "StatID": 5, "Val": 57 }, { "StaffID": 300, "StatID": 6, "Val": 57 }, { "StaffID": 300, "StatID": 7, "Val": 48 }, { "StaffID": 300, "StatID": 8, "Val": 59 }, { "StaffID": 300, "StatID": 9, "Val": 47 }, { "StaffID": 300, "StatID": 10, "Val": 47 }, { "StaffID": 302, "StatID": 2, "Val": 48 }, { "StaffID": 302, "StatID": 3, "Val": 66 }, { "StaffID": 302, "StatID": 4, "Val": 65 }, { "StaffID": 302, "StatID": 5, "Val": 44 }, { "StaffID": 302, "StatID": 6, "Val": 60 }, { "StaffID": 302, "StatID": 7, "Val": 47 }, { "StaffID": 302, "StatID": 8, "Val": 44 }, { "StaffID": 302, "StatID": 9, "Val": 54 }, { "StaffID": 302, "StatID": 10, "Val": 63 }, { "StaffID": 303, "StatID": 2, "Val": 40 }, { "StaffID": 303, "StatID": 3, "Val": 41 }, { "StaffID": 303, "StatID": 4, "Val": 59 }, { "StaffID": 303, "StatID": 5, "Val": 58 }, { "StaffID": 303, "StatID": 6, "Val": 48 }, { "StaffID": 303, "StatID": 7, "Val": 47 }, { "StaffID": 303, "StatID": 8, "Val": 68 }, { "StaffID": 303, "StatID": 9, "Val": 46 }, { "StaffID": 303, "StatID": 10, "Val": 60 }, { "StaffID": 307, "StatID": 2, "Val": 44 }, { "StaffID": 307, "StatID": 3, "Val": 40 }, { "StaffID": 307, "StatID": 4, "Val": 55 }, { "StaffID": 307, "StatID": 5, "Val": 58 }, { "StaffID": 307, "StatID": 6, "Val": 46 }, { "StaffID": 307, "StatID": 7, "Val": 51 }, { "StaffID": 307, "StatID": 8, "Val": 51 }, { "StaffID": 307, "StatID": 9, "Val": 40 }, { "StaffID": 307, "StatID": 10, "Val": 63 }, { "StaffID": 563, "StatID": 13, "Val": 41 }, { "StaffID": 563, "StatID": 25, "Val": 56 }, { "StaffID": 563, "StatID": 43, "Val": 34 }, { "StaffID": 564, "StatID": 19, "Val": 73 }, { "StaffID": 564, "StatID": 20, "Val": 60 }, { "StaffID": 564, "StatID": 26, "Val": 66 }, { "StaffID": 564, "StatID": 27, "Val": 80 }, { "StaffID": 564, "StatID": 28, "Val": 81 }, { "StaffID": 564, "StatID": 29, "Val": 55 }, { "StaffID": 564, "StatID": 30, "Val": 84 }, { "StaffID": 564, "StatID": 31, "Val": 71 }, { "StaffID": 565, "StatID": 13, "Val": 64 }, { "StaffID": 565, "StatID": 25, "Val": 57 }, { "StaffID": 565, "StatID": 43, "Val": 35 }, { "StaffID": 566, "StatID": 11, "Val": 74 }, { "StaffID": 566, "StatID": 22, "Val": 68 }, { "StaffID": 566, "StatID": 23, "Val": 74 }, { "StaffID": 566, "StatID": 24, "Val": 56 } ], "Staff_RaceEngineerDriverAssignments": [ { "DaysTogether": 4021, "DriverID": 1, "IsCurrentAssignment": 0, "RaceEngineerID": 51, "RelationshipLevel": 100 }, { "DaysTogether": 1831, "DriverID": 2, "IsCurrentAssignment": 0, "RaceEngineerID": 6, "RelationshipLevel": 100 }, { "DaysTogether": 786, "DriverID": 3, "IsCurrentAssignment": 1, "RaceEngineerID": 55, "RelationshipLevel": 100 }, { "DaysTogether": 778, "DriverID": 8, "IsCurrentAssignment": 0, "RaceEngineerID": 61, "RelationshipLevel": 99.76004028320312 }, { "DaysTogether": 419, "DriverID": 77, "IsCurrentAssignment": 0, "RaceEngineerID": 63, "RelationshipLevel": 57.6485481262207 }, { "DaysTogether": 2934, "DriverID": 10, "IsCurrentAssignment": 1, "RaceEngineerID": 50, "RelationshipLevel": 100 }, { "DaysTogether": 1101, "DriverID": 11, "IsCurrentAssignment": 0, "RaceEngineerID": 7, "RelationshipLevel": 100 }, { "DaysTogether": 2202, "DriverID": 12, "IsCurrentAssignment": 0, "RaceEngineerID": 48, "RelationshipLevel": 100 }, { "DaysTogether": 419, "DriverID": 102, "IsCurrentAssignment": 0, "RaceEngineerID": 47, "RelationshipLevel": 57.999149322509766 }, { "DaysTogether": 1466, "DriverID": 14, "IsCurrentAssignment": 0, "RaceEngineerID": 54, "RelationshipLevel": 99.77047729492188 }, { "DaysTogether": 42, "DriverID": 76, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 40 }, { "DaysTogether": 1101, "DriverID": 17, "IsCurrentAssignment": 0, "RaceEngineerID": 49, "RelationshipLevel": 100 }, { "DaysTogether": 1831, "DriverID": 18, "IsCurrentAssignment": 0, "RaceEngineerID": 64, "RelationshipLevel": 100 }, { "DaysTogether": 419, "DriverID": 23, "IsCurrentAssignment": 0, "RaceEngineerID": 320, "RelationshipLevel": 58.10194396972656 }, { "DaysTogether": 413, "DriverID": 116, "IsCurrentAssignment": 0, "RaceEngineerID": 56, "RelationshipLevel": 56.688568115234375 }, { "DaysTogether": 413, "DriverID": 83, "IsCurrentAssignment": 0, "RaceEngineerID": 58, "RelationshipLevel": 56.739013671875 }, { "DaysTogether": 1101, "DriverID": 81, "IsCurrentAssignment": 0, "RaceEngineerID": 60, "RelationshipLevel": 100 }, { "DaysTogether": 516, "DriverID": 255, "IsCurrentAssignment": 0, "RaceEngineerID": 321, "RelationshipLevel": 69.27664184570312 }, { "DaysTogether": 365, "DriverID": 117, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 144, "IsCurrentAssignment": 0, "RaceEngineerID": 160, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 121, "IsCurrentAssignment": 0, "RaceEngineerID": 161, "RelationshipLevel": 50 }, { "DaysTogether": 42, "DriverID": 119, "IsCurrentAssignment": 0, "RaceEngineerID": 424, "RelationshipLevel": 6 }, { "DaysTogether": 365, "DriverID": 110, "IsCurrentAssignment": 0, "RaceEngineerID": 158, "RelationshipLevel": 50 }, { "DaysTogether": 407, "DriverID": 130, "IsCurrentAssignment": 0, "RaceEngineerID": 153, "RelationshipLevel": 56 }, { "DaysTogether": 1106, "DriverID": 87, "IsCurrentAssignment": 0, "RaceEngineerID": 146, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 272, "IsCurrentAssignment": 0, "RaceEngineerID": 166, "RelationshipLevel": 50 }, { "DaysTogether": 54, "DriverID": 286, "IsCurrentAssignment": 0, "RaceEngineerID": 343, "RelationshipLevel": 6.875999450683594 }, { "DaysTogether": 365, "DriverID": 280, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 242, "IsCurrentAssignment": 0, "RaceEngineerID": 149, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 288, "IsCurrentAssignment": 0, "RaceEngineerID": 173, "RelationshipLevel": 50 }, { "DaysTogether": 730, "DriverID": 120, "IsCurrentAssignment": 0, "RaceEngineerID": 151, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 115, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 248, "IsCurrentAssignment": 0, "RaceEngineerID": 165, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 252, "IsCurrentAssignment": 0, "RaceEngineerID": 174, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 247, "IsCurrentAssignment": 0, "RaceEngineerID": 169, "RelationshipLevel": 50 }, { "DaysTogether": 741, "DriverID": 135, "IsCurrentAssignment": 0, "RaceEngineerID": 159, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 99, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 50 }, { "DaysTogether": 617, "DriverID": 109, "IsCurrentAssignment": 0, "RaceEngineerID": 168, "RelationshipLevel": 85 }, { "DaysTogether": 365, "DriverID": 279, "IsCurrentAssignment": 0, "RaceEngineerID": 172, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 302, "IsCurrentAssignment": 0, "RaceEngineerID": 163, "RelationshipLevel": 50 }, { "DaysTogether": 419, "DriverID": 287, "IsCurrentAssignment": 0, "RaceEngineerID": 187, "RelationshipLevel": 56.87602233886719 }, { "DaysTogether": 365, "DriverID": 284, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 50 }, { "DaysTogether": 419, "DriverID": 322, "IsCurrentAssignment": 0, "RaceEngineerID": 184, "RelationshipLevel": 56.93864440917969 }, { "DaysTogether": 419, "DriverID": 289, "IsCurrentAssignment": 0, "RaceEngineerID": 178, "RelationshipLevel": 57.14569091796875 }, { "DaysTogether": 365, "DriverID": 305, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 50 }, { "DaysTogether": 550, "DriverID": 77, "IsCurrentAssignment": 0, "RaceEngineerID": 48, "RelationshipLevel": 75 }, { "DaysTogether": 365, "DriverID": 77, "IsCurrentAssignment": 0, "RaceEngineerID": 321, "RelationshipLevel": 50 }, { "DaysTogether": 730, "DriverID": 130, "IsCurrentAssignment": 0, "RaceEngineerID": 155, "RelationshipLevel": 100 }, { "DaysTogether": 1095, "DriverID": 107, "IsCurrentAssignment": 0, "RaceEngineerID": 155, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 13, "IsCurrentAssignment": 0, "RaceEngineerID": 7, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 17, "IsCurrentAssignment": 0, "RaceEngineerID": 50, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 17, "IsCurrentAssignment": 0, "RaceEngineerID": 63, "RelationshipLevel": 50 }, { "DaysTogether": 1095, "DriverID": 15, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 3, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 50 }, { "DaysTogether": 730, "DriverID": 18, "IsCurrentAssignment": 0, "RaceEngineerID": 55, "RelationshipLevel": 100 }, { "DaysTogether": 1095, "DriverID": 23, "IsCurrentAssignment": 0, "RaceEngineerID": 55, "RelationshipLevel": 100 }, { "DaysTogether": 730, "DriverID": 74, "IsCurrentAssignment": 0, "RaceEngineerID": 58, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 253, "IsCurrentAssignment": 0, "RaceEngineerID": 341, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 301, "IsCurrentAssignment": 0, "RaceEngineerID": 364, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 123, "IsCurrentAssignment": 0, "RaceEngineerID": 343, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 114, "IsCurrentAssignment": 0, "RaceEngineerID": 344, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 245, "IsCurrentAssignment": 0, "RaceEngineerID": 345, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 95, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 116, "IsCurrentAssignment": 0, "RaceEngineerID": 149, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 133, "IsCurrentAssignment": 0, "RaceEngineerID": 153, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 106, "IsCurrentAssignment": 0, "RaceEngineerID": 158, "RelationshipLevel": 50 }, { "DaysTogether": 730, "DriverID": 91, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 100 }, { "DaysTogether": 1095, "DriverID": 105, "IsCurrentAssignment": 0, "RaceEngineerID": 159, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 95, "IsCurrentAssignment": 0, "RaceEngineerID": 160, "RelationshipLevel": 50 }, { "DaysTogether": 730, "DriverID": 132, "IsCurrentAssignment": 0, "RaceEngineerID": 169, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 272, "IsCurrentAssignment": 0, "RaceEngineerID": 178, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 119, "IsCurrentAssignment": 0, "RaceEngineerID": 173, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 242, "IsCurrentAssignment": 0, "RaceEngineerID": 172, "RelationshipLevel": 50 }, { "DaysTogether": 377, "DriverID": 127, "IsCurrentAssignment": 0, "RaceEngineerID": 179, "RelationshipLevel": 51.168304443359375 }, { "DaysTogether": 730, "DriverID": 109, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 100 }, { "DaysTogether": 730, "DriverID": 128, "IsCurrentAssignment": 0, "RaceEngineerID": 165, "RelationshipLevel": 100 }, { "DaysTogether": 365, "DriverID": 144, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 244, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 114, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 135, "IsCurrentAssignment": 0, "RaceEngineerID": 178, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 133, "IsCurrentAssignment": 0, "RaceEngineerID": 173, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 127, "IsCurrentAssignment": 0, "RaceEngineerID": 166, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 120, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 110, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 50 }, { "DaysTogether": 365, "DriverID": 12, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 50 }, { "DaysTogether": 407, "DriverID": 282, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 56 }, { "DaysTogether": 730, "DriverID": 255, "IsCurrentAssignment": 0, "RaceEngineerID": 58, "RelationshipLevel": 100 }, { "DaysTogether": 730, "DriverID": 13, "IsCurrentAssignment": 0, "RaceEngineerID": 47, "RelationshipLevel": 100 }, { "DaysTogether": 730, "DriverID": 11, "IsCurrentAssignment": 0, "RaceEngineerID": 47, "RelationshipLevel": 100 }, { "DaysTogether": 620, "DriverID": 15, "IsCurrentAssignment": 0, "RaceEngineerID": 60, "RelationshipLevel": 85 }, { "DaysTogether": 1095, "DriverID": 83, "IsCurrentAssignment": 0, "RaceEngineerID": 321, "RelationshipLevel": 100 }, { "DaysTogether": 7, "DriverID": 109, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 1 }, { "DaysTogether": 365, "DriverID": 95, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 50 }, { "DaysTogether": 306, "DriverID": 244, "IsCurrentAssignment": 0, "RaceEngineerID": 163, "RelationshipLevel": 42 }, { "DaysTogether": 7, "DriverID": 301, "IsCurrentAssignment": 0, "RaceEngineerID": 163, "RelationshipLevel": 1 }, { "DaysTogether": 224, "DriverID": 13, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 35.74013900756836 }, { "DaysTogether": 51, "DriverID": 121, "IsCurrentAssignment": 0, "RaceEngineerID": 151, "RelationshipLevel": 6.678119659423828 }, { "DaysTogether": 57, "DriverID": 281, "IsCurrentAssignment": 0, "RaceEngineerID": 161, "RelationshipLevel": 7.287095069885254 }, { "DaysTogether": 54, "DriverID": 373, "IsCurrentAssignment": 0, "RaceEngineerID": 427, "RelationshipLevel": 7.3319830894470215 }, { "DaysTogether": 46, "DriverID": 95, "IsCurrentAssignment": 1, "RaceEngineerID": 59, "RelationshipLevel": 6.176054954528809 }, { "DaysTogether": 48, "DriverID": 247, "IsCurrentAssignment": 0, "RaceEngineerID": 146, "RelationshipLevel": 6.780781269073486 }, { "DaysTogether": 48, "DriverID": 280, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 6.615480422973633 }, { "DaysTogether": 48, "DriverID": 375, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 6.542401313781738 }, { "DaysTogether": 48, "DriverID": 279, "IsCurrentAssignment": 0, "RaceEngineerID": 159, "RelationshipLevel": 6.83986759185791 }, { "DaysTogether": 48, "DriverID": 374, "IsCurrentAssignment": 0, "RaceEngineerID": 172, "RelationshipLevel": 6.65723991394043 }, { "DaysTogether": 48, "DriverID": 376, "IsCurrentAssignment": 0, "RaceEngineerID": 155, "RelationshipLevel": 6.815855026245117 }, { "DaysTogether": 54, "DriverID": 377, "IsCurrentAssignment": 0, "RaceEngineerID": 149, "RelationshipLevel": 7.418878555297852 }, { "DaysTogether": 42, "DriverID": 117, "IsCurrentAssignment": 0, "RaceEngineerID": 168, "RelationshipLevel": 6 }, { "DaysTogether": 48, "DriverID": 99, "IsCurrentAssignment": 0, "RaceEngineerID": 423, "RelationshipLevel": 6.678119659423828 }, { "DaysTogether": 48, "DriverID": 378, "IsCurrentAssignment": 0, "RaceEngineerID": 341, "RelationshipLevel": 6.5528411865234375 }, { "DaysTogether": 48, "DriverID": 144, "IsCurrentAssignment": 0, "RaceEngineerID": 174, "RelationshipLevel": 6.815855026245117 }, { "DaysTogether": 48, "DriverID": 110, "IsCurrentAssignment": 0, "RaceEngineerID": 160, "RelationshipLevel": 6.667679786682129 }, { "DaysTogether": 48, "DriverID": 252, "IsCurrentAssignment": 0, "RaceEngineerID": 158, "RelationshipLevel": 6.636360168457031 }, { "DaysTogether": 48, "DriverID": 248, "IsCurrentAssignment": 0, "RaceEngineerID": 152, "RelationshipLevel": 6.730319023132324 }, { "DaysTogether": 48, "DriverID": 379, "IsCurrentAssignment": 0, "RaceEngineerID": 165, "RelationshipLevel": 6.587742805480957 }, { "DaysTogether": 54, "DriverID": 380, "IsCurrentAssignment": 0, "RaceEngineerID": 430, "RelationshipLevel": 6.980398178100586 }, { "DaysTogether": 54, "DriverID": 381, "IsCurrentAssignment": 0, "RaceEngineerID": 431, "RelationshipLevel": 7.0099778175354 }, { "DaysTogether": 48, "DriverID": 382, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 6.500638961791992 }, { "DaysTogether": 54, "DriverID": 245, "IsCurrentAssignment": 0, "RaceEngineerID": 422, "RelationshipLevel": 7.376464366912842 }, { "DaysTogether": 61, "DriverID": 288, "IsCurrentAssignment": 0, "RaceEngineerID": 163, "RelationshipLevel": 7.941699981689453 }, { "DaysTogether": 50, "DriverID": 383, "IsCurrentAssignment": 0, "RaceEngineerID": 173, "RelationshipLevel": 6.9041595458984375 }, { "DaysTogether": 54, "DriverID": 283, "IsCurrentAssignment": 0, "RaceEngineerID": 345, "RelationshipLevel": 7.126562118530273 }, { "DaysTogether": 54, "DriverID": 387, "IsCurrentAssignment": 0, "RaceEngineerID": 428, "RelationshipLevel": 7.143716812133789 }, { "DaysTogether": 48, "DriverID": 284, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 6.498898983001709 }, { "DaysTogether": 48, "DriverID": 301, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 6.490199089050293 }, { "DaysTogether": 54, "DriverID": 385, "IsCurrentAssignment": 0, "RaceEngineerID": 364, "RelationshipLevel": 7.043037414550781 }, { "DaysTogether": 48, "DriverID": 306, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 6.531961441040039 }, { "DaysTogether": 54, "DriverID": 386, "IsCurrentAssignment": 0, "RaceEngineerID": 434, "RelationshipLevel": 7.126562118530273 }, { "DaysTogether": 54, "DriverID": 384, "IsCurrentAssignment": 0, "RaceEngineerID": 344, "RelationshipLevel": 6.875999450683594 }, { "DaysTogether": 54, "DriverID": 109, "IsCurrentAssignment": 0, "RaceEngineerID": 420, "RelationshipLevel": 7.2727203369140625 }, { "DaysTogether": 54, "DriverID": 123, "IsCurrentAssignment": 0, "RaceEngineerID": 421, "RelationshipLevel": 7.2814202308654785 }, { "DaysTogether": 54, "DriverID": 388, "IsCurrentAssignment": 0, "RaceEngineerID": 429, "RelationshipLevel": 7.126562118530273 }, { "DaysTogether": 54, "DriverID": 389, "IsCurrentAssignment": 0, "RaceEngineerID": 435, "RelationshipLevel": 6.875999450683594 }, { "DaysTogether": 54, "DriverID": 390, "IsCurrentAssignment": 0, "RaceEngineerID": 193, "RelationshipLevel": 7.197901248931885 }, { "DaysTogether": 48, "DriverID": 105, "IsCurrentAssignment": 0, "RaceEngineerID": 393, "RelationshipLevel": 6.26730489730835 }, { "DaysTogether": 48, "DriverID": 394, "IsCurrentAssignment": 0, "RaceEngineerID": 432, "RelationshipLevel": 6.511078834533691 }, { "DaysTogether": 48, "DriverID": 308, "IsCurrentAssignment": 0, "RaceEngineerID": 195, "RelationshipLevel": 6.542401313781738 }, { "DaysTogether": 54, "DriverID": 305, "IsCurrentAssignment": 0, "RaceEngineerID": 425, "RelationshipLevel": 7.063922882080078 }, { "DaysTogether": 54, "DriverID": 399, "IsCurrentAssignment": 0, "RaceEngineerID": 166, "RelationshipLevel": 7.009037017822266 }, { "DaysTogether": 54, "DriverID": 304, "IsCurrentAssignment": 0, "RaceEngineerID": 433, "RelationshipLevel": 6.875999450683594 }, { "DaysTogether": 115, "DriverID": 15, "IsCurrentAssignment": 0, "RaceEngineerID": 415, "RelationshipLevel": 15.236319541931152 }, { "DaysTogether": 48, "DriverID": 142, "IsCurrentAssignment": 0, "RaceEngineerID": 169, "RelationshipLevel": 6.7678327560424805 }, { "DaysTogether": 48, "DriverID": 242, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 6.751198768615723 }, { "DaysTogether": 54, "DriverID": 282, "IsCurrentAssignment": 0, "RaceEngineerID": 426, "RelationshipLevel": 7.2235107421875 }, { "DaysTogether": 12, "DriverID": 413, "IsCurrentAssignment": 0, "RaceEngineerID": 168, "RelationshipLevel": 0.9713850021362305 }, { "DaysTogether": 12, "DriverID": 130, "IsCurrentAssignment": 0, "RaceEngineerID": 424, "RelationshipLevel": 1.1018850803375244 }, { "DaysTogether": 8, "DriverID": 11, "IsCurrentAssignment": 1, "RaceEngineerID": 56, "RelationshipLevel": 0.9563600420951843 }, { "DaysTogether": 2, "DriverID": 23, "IsCurrentAssignment": 1, "RaceEngineerID": 51, "RelationshipLevel": 0.3008599877357483 }, { "DaysTogether": 2, "DriverID": 376, "IsCurrentAssignment": 1, "RaceEngineerID": 320, "RelationshipLevel": 0.3479899764060974 }, { "DaysTogether": 2, "DriverID": 15, "IsCurrentAssignment": 1, "RaceEngineerID": 54, "RelationshipLevel": 0.23995999991893768 }, { "DaysTogether": 0, "DriverID": 135, "IsCurrentAssignment": 0, "RaceEngineerID": 415, "RelationshipLevel": 0 }, { "DaysTogether": 2, "DriverID": 12, "IsCurrentAssignment": 1, "RaceEngineerID": 47, "RelationshipLevel": 0.3564999997615814 }, { "DaysTogether": 2, "DriverID": 102, "IsCurrentAssignment": 1, "RaceEngineerID": 48, "RelationshipLevel": 0.3239780068397522 }, { "DaysTogether": 0, "DriverID": 2, "IsCurrentAssignment": 0, "RaceEngineerID": 7, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 1, "IsCurrentAssignment": 0, "RaceEngineerID": 622, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 95, "IsCurrentAssignment": 0, "RaceEngineerID": 623, "RelationshipLevel": 1.0279619693756104 }, { "DaysTogether": 8, "DriverID": 18, "IsCurrentAssignment": 1, "RaceEngineerID": 58, "RelationshipLevel": 1.2118698358535767 }, { "DaysTogether": 0, "DriverID": 142, "IsCurrentAssignment": 0, "RaceEngineerID": 624, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 14, "IsCurrentAssignment": 0, "RaceEngineerID": 625, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 81, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 144, "IsCurrentAssignment": 0, "RaceEngineerID": 626, "RelationshipLevel": 0 }, { "DaysTogether": 8, "DriverID": 83, "IsCurrentAssignment": 1, "RaceEngineerID": 643, "RelationshipLevel": 0.8472749590873718 }, { "DaysTogether": 8, "DriverID": 279, "IsCurrentAssignment": 1, "RaceEngineerID": 644, "RelationshipLevel": 0.7476800084114075 }, { "DaysTogether": 0, "DriverID": 289, "IsCurrentAssignment": 0, "RaceEngineerID": 158, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 119, "IsCurrentAssignment": 0, "RaceEngineerID": 158, "RelationshipLevel": 0.6363600492477417 }, { "DaysTogether": 0, "DriverID": 245, "IsCurrentAssignment": 0, "RaceEngineerID": 174, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 373, "IsCurrentAssignment": 0, "RaceEngineerID": 174, "RelationshipLevel": 0.7094399929046631 }, { "DaysTogether": 2, "DriverID": 288, "IsCurrentAssignment": 1, "RaceEngineerID": 152, "RelationshipLevel": 0.24344000220298767 }, { "DaysTogether": 6, "DriverID": 99, "IsCurrentAssignment": 0, "RaceEngineerID": 152, "RelationshipLevel": 0.7303200364112854 }, { "DaysTogether": 0, "DriverID": 322, "IsCurrentAssignment": 0, "RaceEngineerID": 160, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 282, "IsCurrentAssignment": 0, "RaceEngineerID": 160, "RelationshipLevel": 0.7678319811820984 }, { "DaysTogether": 0, "DriverID": 301, "IsCurrentAssignment": 0, "RaceEngineerID": 155, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 280, "IsCurrentAssignment": 0, "RaceEngineerID": 155, "RelationshipLevel": 0.7094399929046631 }, { "DaysTogether": 6, "DriverID": 252, "IsCurrentAssignment": 0, "RaceEngineerID": 151, "RelationshipLevel": 0.6781200170516968 }, { "DaysTogether": 0, "DriverID": 127, "IsCurrentAssignment": 0, "RaceEngineerID": 146, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 377, "IsCurrentAssignment": 0, "RaceEngineerID": 146, "RelationshipLevel": 0.7825199365615845 }, { "DaysTogether": 0, "DriverID": 110, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 399, "IsCurrentAssignment": 0, "RaceEngineerID": 148, "RelationshipLevel": 0.8638800382614136 }, { "DaysTogether": 8, "DriverID": 385, "IsCurrentAssignment": 1, "RaceEngineerID": 341, "RelationshipLevel": 0.7493000030517578 }, { "DaysTogether": 0, "DriverID": 374, "IsCurrentAssignment": 0, "RaceEngineerID": 423, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 390, "IsCurrentAssignment": 0, "RaceEngineerID": 423, "RelationshipLevel": 0.6781200170516968 }, { "DaysTogether": 6, "DriverID": 601, "IsCurrentAssignment": 0, "RaceEngineerID": 421, "RelationshipLevel": 0.6450600028038025 }, { "DaysTogether": 0, "DriverID": 602, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 380, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 0 }, { "DaysTogether": 2, "DriverID": 411, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 0.23593400418758392 }, { "DaysTogether": 2, "DriverID": 610, "IsCurrentAssignment": 1, "RaceEngineerID": 172, "RelationshipLevel": 0.21908000111579895 }, { "DaysTogether": 0, "DriverID": 604, "IsCurrentAssignment": 0, "RaceEngineerID": 172, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 382, "IsCurrentAssignment": 0, "RaceEngineerID": 172, "RelationshipLevel": 0.6572399735450745 }, { "DaysTogether": 0, "DriverID": 381, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 439, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 611, "IsCurrentAssignment": 0, "RaceEngineerID": 181, "RelationshipLevel": 0.5006399750709534 }, { "DaysTogether": 0, "DriverID": 283, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 394, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 286, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 0.4901999831199646 }, { "DaysTogether": 0, "DriverID": 386, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 612, "IsCurrentAssignment": 0, "RaceEngineerID": 183, "RelationshipLevel": 0.5424000024795532 }, { "DaysTogether": 0, "DriverID": 613, "IsCurrentAssignment": 1, "RaceEngineerID": 165, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 614, "IsCurrentAssignment": 0, "RaceEngineerID": 165, "RelationshipLevel": 0.5110799670219421 }, { "DaysTogether": 0, "DriverID": 615, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 383, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 616, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 0.5319600105285645 }, { "DaysTogether": 0, "DriverID": 384, "IsCurrentAssignment": 0, "RaceEngineerID": 195, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 617, "IsCurrentAssignment": 0, "RaceEngineerID": 195, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 618, "IsCurrentAssignment": 0, "RaceEngineerID": 195, "RelationshipLevel": 0.5424000024795532 }, { "DaysTogether": 0, "DriverID": 619, "IsCurrentAssignment": 0, "RaceEngineerID": 432, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 620, "IsCurrentAssignment": 0, "RaceEngineerID": 432, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 304, "IsCurrentAssignment": 0, "RaceEngineerID": 432, "RelationshipLevel": 0.5110799670219421 }, { "DaysTogether": 0, "DriverID": 406, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 388, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 0 }, { "DaysTogether": 6, "DriverID": 621, "IsCurrentAssignment": 0, "RaceEngineerID": 191, "RelationshipLevel": 0.5006399750709534 }, { "DaysTogether": 8, "DriverID": 2, "IsCurrentAssignment": 1, "RaceEngineerID": 622, "RelationshipLevel": 1.3171639442443848 }, { "DaysTogether": 6, "DriverID": 1, "IsCurrentAssignment": 0, "RaceEngineerID": 7, "RelationshipLevel": 1.0559760332107544 }, { "DaysTogether": 6, "DriverID": 376, "IsCurrentAssignment": 0, "RaceEngineerID": 51, "RelationshipLevel": 0.9324960112571716 }, { "DaysTogether": 6, "DriverID": 135, "IsCurrentAssignment": 0, "RaceEngineerID": 54, "RelationshipLevel": 0.7181400060653687 }, { "DaysTogether": 8, "DriverID": 14, "IsCurrentAssignment": 1, "RaceEngineerID": 624, "RelationshipLevel": 0.8415799736976624 }, { "DaysTogether": 8, "DriverID": 142, "IsCurrentAssignment": 1, "RaceEngineerID": 625, "RelationshipLevel": 0.7512000203132629 }, { "DaysTogether": 6, "DriverID": 144, "IsCurrentAssignment": 0, "RaceEngineerID": 59, "RelationshipLevel": 0.7111799716949463 }, { "DaysTogether": 6, "DriverID": 81, "IsCurrentAssignment": 0, "RaceEngineerID": 626, "RelationshipLevel": 0.7390199899673462 }, { "DaysTogether": 6, "DriverID": 301, "IsCurrentAssignment": 1, "RaceEngineerID": 169, "RelationshipLevel": 0.667680025100708 }, { "DaysTogether": 4, "DriverID": 374, "IsCurrentAssignment": 0, "RaceEngineerID": 173, "RelationshipLevel": 0.45208001136779785 }, { "DaysTogether": 4, "DriverID": 602, "IsCurrentAssignment": 0, "RaceEngineerID": 556, "RelationshipLevel": 0.2919999957084656 }, { "DaysTogether": 4, "DriverID": 610, "IsCurrentAssignment": 0, "RaceEngineerID": 555, "RelationshipLevel": 0.3248499929904938 }, { "DaysTogether": 2, "DriverID": 77, "IsCurrentAssignment": 1, "RaceEngineerID": 395, "RelationshipLevel": 0.25387999415397644 }, { "DaysTogether": 2, "DriverID": 8, "IsCurrentAssignment": 1, "RaceEngineerID": 415, "RelationshipLevel": 0.1695300042629242 }, { "DaysTogether": 2, "DriverID": 144, "IsCurrentAssignment": 1, "RaceEngineerID": 623, "RelationshipLevel": 0.3279799818992615 }, { "DaysTogether": 2, "DriverID": 373, "IsCurrentAssignment": 1, "RaceEngineerID": 626, "RelationshipLevel": 0.21040000021457672 }, { "DaysTogether": 2, "DriverID": 1, "IsCurrentAssignment": 1, "RaceEngineerID": 680, "RelationshipLevel": 0.1678999960422516 }, { "DaysTogether": 2, "DriverID": 17, "IsCurrentAssignment": 1, "RaceEngineerID": 682, "RelationshipLevel": 0.10949999839067459 }, { "DaysTogether": 2, "DriverID": 248, "IsCurrentAssignment": 1, "RaceEngineerID": 684, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 283, "IsCurrentAssignment": 1, "RaceEngineerID": 156, "RelationshipLevel": 0.24692000448703766 }, { "DaysTogether": 2, "DriverID": 604, "IsCurrentAssignment": 1, "RaceEngineerID": 159, "RelationshipLevel": 0.24692000448703766 }, { "DaysTogether": 2, "DriverID": 378, "IsCurrentAssignment": 1, "RaceEngineerID": 158, "RelationshipLevel": 0.24393798410892487 }, { "DaysTogether": 2, "DriverID": 375, "IsCurrentAssignment": 1, "RaceEngineerID": 148, "RelationshipLevel": 0.25040000677108765 }, { "DaysTogether": 2, "DriverID": 399, "IsCurrentAssignment": 1, "RaceEngineerID": 435, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 377, "IsCurrentAssignment": 1, "RaceEngineerID": 161, "RelationshipLevel": 0.21908000111579895 }, { "DaysTogether": 2, "DriverID": 645, "IsCurrentAssignment": 1, "RaceEngineerID": 160, "RelationshipLevel": 0.22256000339984894 }, { "DaysTogether": 2, "DriverID": 252, "IsCurrentAssignment": 1, "RaceEngineerID": 179, "RelationshipLevel": 0.19472000002861023 }, { "DaysTogether": 2, "DriverID": 394, "IsCurrentAssignment": 1, "RaceEngineerID": 426, "RelationshipLevel": 0.17732000350952148 }, { "DaysTogether": 2, "DriverID": 280, "IsCurrentAssignment": 1, "RaceEngineerID": 153, "RelationshipLevel": 0.22951999306678772 }, { "DaysTogether": 2, "DriverID": 646, "IsCurrentAssignment": 1, "RaceEngineerID": 149, "RelationshipLevel": 0.2364799976348877 }, { "DaysTogether": 2, "DriverID": 282, "IsCurrentAssignment": 1, "RaceEngineerID": 151, "RelationshipLevel": 0.22604000568389893 }, { "DaysTogether": 2, "DriverID": 618, "IsCurrentAssignment": 1, "RaceEngineerID": 429, "RelationshipLevel": 0.18775999546051025 }, { "DaysTogether": 2, "DriverID": 380, "IsCurrentAssignment": 1, "RaceEngineerID": 174, "RelationshipLevel": 0.2364799976348877 }, { "DaysTogether": 2, "DriverID": 286, "IsCurrentAssignment": 1, "RaceEngineerID": 422, "RelationshipLevel": 0.2016800045967102 }, { "DaysTogether": 2, "DriverID": 647, "IsCurrentAssignment": 0, "RaceEngineerID": 175, "RelationshipLevel": 0.17732000350952148 }, { "DaysTogether": 2, "DriverID": 123, "IsCurrentAssignment": 0, "RaceEngineerID": 423, "RelationshipLevel": 0.22604000568389893 }, { "DaysTogether": 2, "DriverID": 381, "IsCurrentAssignment": 1, "RaceEngineerID": 146, "RelationshipLevel": 0.2608399987220764 }, { "DaysTogether": 2, "DriverID": 601, "IsCurrentAssignment": 1, "RaceEngineerID": 424, "RelationshipLevel": 0.21908000111579895 }, { "DaysTogether": 2, "DriverID": 617, "IsCurrentAssignment": 1, "RaceEngineerID": 427, "RelationshipLevel": 0.208639994263649 }, { "DaysTogether": 2, "DriverID": 611, "IsCurrentAssignment": 1, "RaceEngineerID": 343, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 658, "IsCurrentAssignment": 0, "RaceEngineerID": 364, "RelationshipLevel": 0.1738400012254715 }, { "DaysTogether": 2, "DriverID": 649, "IsCurrentAssignment": 1, "RaceEngineerID": 173, "RelationshipLevel": 0.22604000568389893 }, { "DaysTogether": 2, "DriverID": 650, "IsCurrentAssignment": 1, "RaceEngineerID": 178, "RelationshipLevel": 0.21992599964141846 }, { "DaysTogether": 2, "DriverID": 652, "IsCurrentAssignment": 1, "RaceEngineerID": 181, "RelationshipLevel": 0.16687999665737152 }, { "DaysTogether": 2, "DriverID": 653, "IsCurrentAssignment": 1, "RaceEngineerID": 431, "RelationshipLevel": 0.1703599989414215 }, { "DaysTogether": 2, "DriverID": 654, "IsCurrentAssignment": 0, "RaceEngineerID": 425, "RelationshipLevel": 0.17732000350952148 }, { "DaysTogether": 2, "DriverID": 659, "IsCurrentAssignment": 0, "RaceEngineerID": 187, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 660, "IsCurrentAssignment": 0, "RaceEngineerID": 184, "RelationshipLevel": 0.15644000470638275 }, { "DaysTogether": 2, "DriverID": 661, "IsCurrentAssignment": 0, "RaceEngineerID": 165, "RelationshipLevel": 0.1703599989414215 }, { "DaysTogether": 2, "DriverID": 655, "IsCurrentAssignment": 0, "RaceEngineerID": 430, "RelationshipLevel": 0.16339999437332153 }, { "DaysTogether": 2, "DriverID": 656, "IsCurrentAssignment": 0, "RaceEngineerID": 162, "RelationshipLevel": 0.16339999437332153 }, { "DaysTogether": 2, "DriverID": 613, "IsCurrentAssignment": 0, "RaceEngineerID": 421, "RelationshipLevel": 0.21559999883174896 }, { "DaysTogether": 2, "DriverID": 651, "IsCurrentAssignment": 1, "RaceEngineerID": 166, "RelationshipLevel": 0.15644000470638275 }, { "DaysTogether": 2, "DriverID": 439, "IsCurrentAssignment": 1, "RaceEngineerID": 428, "RelationshipLevel": 0.17732000350952148 }, { "DaysTogether": 2, "DriverID": 614, "IsCurrentAssignment": 1, "RaceEngineerID": 338, "RelationshipLevel": 0.22604000568389893 }, { "DaysTogether": 2, "DriverID": 615, "IsCurrentAssignment": 1, "RaceEngineerID": 163, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 411, "IsCurrentAssignment": 0, "RaceEngineerID": 555, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 648, "IsCurrentAssignment": 0, "RaceEngineerID": 433, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 657, "IsCurrentAssignment": 1, "RaceEngineerID": 344, "RelationshipLevel": 0.1459999978542328 }, { "DaysTogether": 2, "DriverID": 621, "IsCurrentAssignment": 1, "RaceEngineerID": 195, "RelationshipLevel": 0.18080000579357147 }, { "DaysTogether": 2, "DriverID": 602, "IsCurrentAssignment": 0, "RaceEngineerID": 193, "RelationshipLevel": 0.2016800045967102 }, { "DaysTogether": 2, "DriverID": 406, "IsCurrentAssignment": 1, "RaceEngineerID": 145, "RelationshipLevel": 0.19820000231266022 }, { "DaysTogether": 2, "DriverID": 664, "IsCurrentAssignment": 1, "RaceEngineerID": 420, "RelationshipLevel": 0.21211999654769897 }, { "DaysTogether": 2, "DriverID": 612, "IsCurrentAssignment": 0, "RaceEngineerID": 361, "RelationshipLevel": 0.2051600068807602 }, { "DaysTogether": 2, "DriverID": 253, "IsCurrentAssignment": 1, "RaceEngineerID": 432, "RelationshipLevel": 0.1703599989414215 }, { "DaysTogether": 2, "DriverID": 662, "IsCurrentAssignment": 1, "RaceEngineerID": 191, "RelationshipLevel": 0.16687999665737152 }, { "DaysTogether": 2, "DriverID": 663, "IsCurrentAssignment": 0, "RaceEngineerID": 345, "RelationshipLevel": 0.18775999546051025 }, { "DaysTogether": 0, "DriverID": 647, "IsCurrentAssignment": 1, "RaceEngineerID": 421, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 123, "IsCurrentAssignment": 1, "RaceEngineerID": 175, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 658, "IsCurrentAssignment": 1, "RaceEngineerID": 361, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 654, "IsCurrentAssignment": 1, "RaceEngineerID": 433, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 659, "IsCurrentAssignment": 1, "RaceEngineerID": 184, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 660, "IsCurrentAssignment": 1, "RaceEngineerID": 364, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 661, "IsCurrentAssignment": 1, "RaceEngineerID": 183, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 655, "IsCurrentAssignment": 1, "RaceEngineerID": 423, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 656, "IsCurrentAssignment": 1, "RaceEngineerID": 187, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 411, "IsCurrentAssignment": 1, "RaceEngineerID": 162, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 648, "IsCurrentAssignment": 1, "RaceEngineerID": 345, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 602, "IsCurrentAssignment": 1, "RaceEngineerID": 555, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 612, "IsCurrentAssignment": 1, "RaceEngineerID": 193, "RelationshipLevel": 0 }, { "DaysTogether": 0, "DriverID": 663, "IsCurrentAssignment": 1, "RaceEngineerID": 425, "RelationshipLevel": 0 } ], "Staff_NarrativeData": [ { "StaffID": 323, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 1, "IsActive": 1 }, { "StaffID": 324, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 2, "IsActive": 1 }, { "StaffID": 290, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 3, "IsActive": 1 }, { "StaffID": 326, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 4, "IsActive": 1 }, { "StaffID": 676, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 5, "IsActive": 1 }, { "StaffID": 328, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 6, "IsActive": 1 }, { "StaffID": 412, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 7, "IsActive": 1 }, { "StaffID": 295, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 8, "IsActive": 1 }, { "StaffID": 291, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 9, "IsActive": 1 }, { "StaffID": 675, "GenSource": 2, "JobTitle": "[STAFF_TYPE_5]", "TeamID": 10, "IsActive": 1 } ], "Staff_State": [ { "Mentality": 38, "MentalityOpinion": 2, "StaffID": 1, "UnspentXP": 628, "XPGainedLastRace": 0, "XPGainedLastWeek": 22 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 2, "UnspentXP": 604, "XPGainedLastRace": 0, "XPGainedLastWeek": 58 }, { "Mentality": 93, "MentalityOpinion": 0, "StaffID": 3, "UnspentXP": 634, "XPGainedLastRace": 0, "XPGainedLastWeek": 51 }, { "Mentality": 6, "MentalityOpinion": 4, "StaffID": 4, "UnspentXP": 450, "XPGainedLastRace": 0, "XPGainedLastWeek": 116 }, { "Mentality": 43, "MentalityOpinion": 2, "StaffID": 6, "UnspentXP": 767, "XPGainedLastRace": 0, "XPGainedLastWeek": 140 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 7, "UnspentXP": 1208, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 20, "MentalityOpinion": 3, "StaffID": 8, "UnspentXP": 121, "XPGainedLastRace": 0, "XPGainedLastWeek": 74 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 9, "UnspentXP": 863, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 56, "MentalityOpinion": 2, "StaffID": 10, "UnspentXP": 156, "XPGainedLastRace": 0, "XPGainedLastWeek": 66 }, { "Mentality": 20, "MentalityOpinion": 3, "StaffID": 11, "UnspentXP": 311, "XPGainedLastRace": 0, "XPGainedLastWeek": 31 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 12, "UnspentXP": 830, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 13, "UnspentXP": 50, "XPGainedLastRace": 0, "XPGainedLastWeek": 21 }, { "Mentality": 56, "MentalityOpinion": 2, "StaffID": 14, "UnspentXP": 514, "XPGainedLastRace": 0, "XPGainedLastWeek": 31 }, { "Mentality": 41, "MentalityOpinion": 2, "StaffID": 15, "UnspentXP": 522, "XPGainedLastRace": 0, "XPGainedLastWeek": 47 }, { "Mentality": 3, "MentalityOpinion": 4, "StaffID": 17, "UnspentXP": 626, "XPGainedLastRace": 0, "XPGainedLastWeek": 32 }, { "Mentality": 67, "MentalityOpinion": 1, "StaffID": 18, "UnspentXP": 544, "XPGainedLastRace": 0, "XPGainedLastWeek": 42 }, { "Mentality": 43, "MentalityOpinion": 2, "StaffID": 20, "UnspentXP": 226, "XPGainedLastRace": 0, "XPGainedLastWeek": 102 }, { "Mentality": 57, "MentalityOpinion": 2, "StaffID": 22, "UnspentXP": 764, "XPGainedLastRace": 0, "XPGainedLastWeek": 97 }, { "Mentality": 78, "MentalityOpinion": 1, "StaffID": 23, "UnspentXP": 694, "XPGainedLastRace": 0, "XPGainedLastWeek": 68 }, { "Mentality": 43, "MentalityOpinion": 2, "StaffID": 26, "UnspentXP": 1184, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 28, "UnspentXP": 1083, "XPGainedLastRace": 0, "XPGainedLastWeek": 140 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 30, "UnspentXP": 502, "XPGainedLastRace": 0, "XPGainedLastWeek": 120 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 33, "UnspentXP": 131, "XPGainedLastRace": 0, "XPGainedLastWeek": 180 }, { "Mentality": 28, "MentalityOpinion": 3, "StaffID": 37, "UnspentXP": 672, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 49, "MentalityOpinion": 2, "StaffID": 38, "UnspentXP": 844, "XPGainedLastRace": 0, "XPGainedLastWeek": 140 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 39, "UnspentXP": 779, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 29, "MentalityOpinion": 3, "StaffID": 40, "UnspentXP": 311, "XPGainedLastRace": 0, "XPGainedLastWeek": 100 }, { "Mentality": 79, "MentalityOpinion": 1, "StaffID": 43, "UnspentXP": 509, "XPGainedLastRace": 0, "XPGainedLastWeek": 120 }, { "Mentality": 67, "MentalityOpinion": 1, "StaffID": 44, "UnspentXP": 752, "XPGainedLastRace": 0, "XPGainedLastWeek": 160 }, { "Mentality": 97, "MentalityOpinion": 0, "StaffID": 47, "UnspentXP": 482, "XPGainedLastRace": 0, "XPGainedLastWeek": 158 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 48, "UnspentXP": 397, "XPGainedLastRace": 0, "XPGainedLastWeek": 158 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 49, "UnspentXP": 948, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 40, "MentalityOpinion": 2, "StaffID": 50, "UnspentXP": 1081, "XPGainedLastRace": 0, "XPGainedLastWeek": 140 }, { "Mentality": 49, "MentalityOpinion": 2, "StaffID": 51, "UnspentXP": 499, "XPGainedLastRace": 0, "XPGainedLastWeek": 140 }, { "Mentality": 53, "MentalityOpinion": 2, "StaffID": 54, "UnspentXP": 707, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 55, "UnspentXP": 621, "XPGainedLastRace": 0, "XPGainedLastWeek": 160 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 56, "UnspentXP": 402, "XPGainedLastRace": 0, "XPGainedLastWeek": 160 }, { "Mentality": 91, "MentalityOpinion": 0, "StaffID": 58, "UnspentXP": 140, "XPGainedLastRace": 0, "XPGainedLastWeek": 167 }, { "Mentality": 38, "MentalityOpinion": 2, "StaffID": 59, "UnspentXP": 880, "XPGainedLastRace": 0, "XPGainedLastWeek": 180 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 60, "UnspentXP": 374, "XPGainedLastRace": 0, "XPGainedLastWeek": 180 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 61, "UnspentXP": 1000, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 63, "UnspentXP": 1133, "XPGainedLastRace": 0, "XPGainedLastWeek": 152 }, { "Mentality": 43, "MentalityOpinion": 2, "StaffID": 64, "UnspentXP": 310, "XPGainedLastRace": 0, "XPGainedLastWeek": 144 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 74, "UnspentXP": 638, "XPGainedLastRace": 0, "XPGainedLastWeek": 108 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 76, "UnspentXP": 11, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 16, "MentalityOpinion": 3, "StaffID": 77, "UnspentXP": 447, "XPGainedLastRace": 0, "XPGainedLastWeek": 20 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 78, "UnspentXP": 860, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 25, "MentalityOpinion": 3, "StaffID": 80, "UnspentXP": 341, "XPGainedLastRace": 0, "XPGainedLastWeek": 172 }, { "Mentality": 73, "MentalityOpinion": 1, "StaffID": 81, "UnspentXP": 464, "XPGainedLastRace": 0, "XPGainedLastWeek": 59 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 82, "UnspentXP": 485, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 56, "MentalityOpinion": 2, "StaffID": 83, "UnspentXP": 360, "XPGainedLastRace": 0, "XPGainedLastWeek": 22 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 87, "UnspentXP": 1406, "XPGainedLastRace": 0, "XPGainedLastWeek": 486 }, { "Mentality": 55, "MentalityOpinion": 2, "StaffID": 88, "UnspentXP": 103, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 91, "UnspentXP": 523, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 2, "MentalityOpinion": 4, "StaffID": 95, "UnspentXP": 698, "XPGainedLastRace": 0, "XPGainedLastWeek": 91 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 99, "UnspentXP": 642, "XPGainedLastRace": 0, "XPGainedLastWeek": 76 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 102, "UnspentXP": 1970, "XPGainedLastRace": 0, "XPGainedLastWeek": 714 }, { "Mentality": 20, "MentalityOpinion": 3, "StaffID": 105, "UnspentXP": 777, "XPGainedLastRace": 0, "XPGainedLastWeek": 131 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 106, "UnspentXP": 1250, "XPGainedLastRace": 0, "XPGainedLastWeek": 340 }, { "Mentality": 89, "MentalityOpinion": 1, "StaffID": 107, "UnspentXP": 939, "XPGainedLastRace": 0, "XPGainedLastWeek": 450 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 109, "UnspentXP": 452, "XPGainedLastRace": 0, "XPGainedLastWeek": 70 }, { "Mentality": 40, "MentalityOpinion": 2, "StaffID": 110, "UnspentXP": 902, "XPGainedLastRace": 0, "XPGainedLastWeek": 76 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 114, "UnspentXP": 788, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 115, "UnspentXP": 749, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 40, "MentalityOpinion": 2, "StaffID": 116, "UnspentXP": 550, "XPGainedLastRace": 0, "XPGainedLastWeek": 51 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 117, "UnspentXP": 132, "XPGainedLastRace": 0, "XPGainedLastWeek": 74 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 119, "UnspentXP": 67, "XPGainedLastRace": 0, "XPGainedLastWeek": 178 }, { "Mentality": 38, "MentalityOpinion": 2, "StaffID": 120, "UnspentXP": 749, "XPGainedLastRace": 0, "XPGainedLastWeek": 232 }, { "Mentality": 73, "MentalityOpinion": 1, "StaffID": 121, "UnspentXP": 929, "XPGainedLastRace": 0, "XPGainedLastWeek": 284 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 123, "UnspentXP": 964, "XPGainedLastRace": 0, "XPGainedLastWeek": 78 }, { "Mentality": 55, "MentalityOpinion": 2, "StaffID": 127, "UnspentXP": 533, "XPGainedLastRace": 0, "XPGainedLastWeek": 528 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 128, "UnspentXP": 701, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 130, "UnspentXP": 453, "XPGainedLastRace": 0, "XPGainedLastWeek": 87 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 132, "UnspentXP": 671, "XPGainedLastRace": 0, "XPGainedLastWeek": 113 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 133, "UnspentXP": 446, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 47, "MentalityOpinion": 2, "StaffID": 135, "UnspentXP": 924, "XPGainedLastRace": 0, "XPGainedLastWeek": 57 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 140, "UnspentXP": 885, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 142, "UnspentXP": 1123, "XPGainedLastRace": 0, "XPGainedLastWeek": 296 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 143, "UnspentXP": 868, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 144, "UnspentXP": 1097, "XPGainedLastRace": 0, "XPGainedLastWeek": 133 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 145, "UnspentXP": 166, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 146, "UnspentXP": 193, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 148, "UnspentXP": 936, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 149, "UnspentXP": 907, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 151, "UnspentXP": 382, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 152, "UnspentXP": 1059, "XPGainedLastRace": 0, "XPGainedLastWeek": 115 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 153, "UnspentXP": 264, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 155, "UnspentXP": 504, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 156, "UnspentXP": 945, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 158, "UnspentXP": 22, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 159, "UnspentXP": 64, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 160, "UnspentXP": 805, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 161, "UnspentXP": 856, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 162, "UnspentXP": 674, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 163, "UnspentXP": 485, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 165, "UnspentXP": 367, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 166, "UnspentXP": 344, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 168, "UnspentXP": 230, "XPGainedLastRace": 0, "XPGainedLastWeek": 114 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 169, "UnspentXP": 382, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 172, "UnspentXP": 985, "XPGainedLastRace": 0, "XPGainedLastWeek": 105 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 173, "UnspentXP": 118, "XPGainedLastRace": 0, "XPGainedLastWeek": 105 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 174, "UnspentXP": 709, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 175, "UnspentXP": 461, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 178, "UnspentXP": 169, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 179, "UnspentXP": 736, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 181, "UnspentXP": 197, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 183, "UnspentXP": 439, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 184, "UnspentXP": 716, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 187, "UnspentXP": 890, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 191, "UnspentXP": 762, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 193, "UnspentXP": 24, "XPGainedLastRace": 0, "XPGainedLastWeek": 105 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 195, "UnspentXP": 523, "XPGainedLastRace": 0, "XPGainedLastWeek": 105 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 242, "UnspentXP": 407, "XPGainedLastRace": 0, "XPGainedLastWeek": 172 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 244, "UnspentXP": 890, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 245, "UnspentXP": 149, "XPGainedLastRace": 0, "XPGainedLastWeek": 297 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 247, "UnspentXP": 996, "XPGainedLastRace": 0, "XPGainedLastWeek": 395 }, { "Mentality": 32, "MentalityOpinion": 3, "StaffID": 248, "UnspentXP": 1372, "XPGainedLastRace": 0, "XPGainedLastWeek": 321 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 252, "UnspentXP": 646, "XPGainedLastRace": 0, "XPGainedLastWeek": 96 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 253, "UnspentXP": 847, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 44, "MentalityOpinion": 2, "StaffID": 255, "UnspentXP": 589, "XPGainedLastRace": 0, "XPGainedLastWeek": 29 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 258, "UnspentXP": 482, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 259, "UnspentXP": 861, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 260, "UnspentXP": 902, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 263, "UnspentXP": 1, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 73, "MentalityOpinion": 1, "StaffID": 264, "UnspentXP": 165, "XPGainedLastRace": 0, "XPGainedLastWeek": 84 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 272, "UnspentXP": 249, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 279, "UnspentXP": 1172, "XPGainedLastRace": 0, "XPGainedLastWeek": 213 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 280, "UnspentXP": 10, "XPGainedLastRace": 0, "XPGainedLastWeek": 593 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 281, "UnspentXP": 648, "XPGainedLastRace": 0, "XPGainedLastWeek": 235 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 282, "UnspentXP": 373, "XPGainedLastRace": 0, "XPGainedLastWeek": 564 }, { "Mentality": 73, "MentalityOpinion": 1, "StaffID": 283, "UnspentXP": 382, "XPGainedLastRace": 0, "XPGainedLastWeek": 146 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 284, "UnspentXP": 1056, "XPGainedLastRace": 0, "XPGainedLastWeek": 280 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 285, "UnspentXP": 483, "XPGainedLastRace": 0, "XPGainedLastWeek": 579 }, { "Mentality": 73, "MentalityOpinion": 1, "StaffID": 286, "UnspentXP": 711, "XPGainedLastRace": 0, "XPGainedLastWeek": 405 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 287, "UnspentXP": 213, "XPGainedLastRace": 0, "XPGainedLastWeek": 161 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 288, "UnspentXP": 588, "XPGainedLastRace": 0, "XPGainedLastWeek": 297 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 289, "UnspentXP": 976, "XPGainedLastRace": 0, "XPGainedLastWeek": 172 }, { "Mentality": 84, "MentalityOpinion": 1, "StaffID": 292, "UnspentXP": 748, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 28, "MentalityOpinion": 3, "StaffID": 293, "UnspentXP": 975, "XPGainedLastRace": 0, "XPGainedLastWeek": 171 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 299, "UnspentXP": 877, "XPGainedLastRace": 0, "XPGainedLastWeek": 113 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 300, "UnspentXP": 327, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 301, "UnspentXP": 16, "XPGainedLastRace": 0, "XPGainedLastWeek": 234 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 302, "UnspentXP": 444, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 303, "UnspentXP": 78, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 304, "UnspentXP": 810, "XPGainedLastRace": 0, "XPGainedLastWeek": 260 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 305, "UnspentXP": 80, "XPGainedLastRace": 0, "XPGainedLastWeek": 400 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 306, "UnspentXP": 784, "XPGainedLastRace": 0, "XPGainedLastWeek": 157 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 307, "UnspentXP": 888, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 308, "UnspentXP": 507, "XPGainedLastRace": 0, "XPGainedLastWeek": 190 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 309, "UnspentXP": 455, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 310, "UnspentXP": 698, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 311, "UnspentXP": 904, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 312, "UnspentXP": 270, "XPGainedLastRace": 0, "XPGainedLastWeek": 185 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 313, "UnspentXP": 40, "XPGainedLastRace": 0, "XPGainedLastWeek": 114 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 314, "UnspentXP": 1036, "XPGainedLastRace": 0, "XPGainedLastWeek": 145 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 315, "UnspentXP": 591, "XPGainedLastRace": 0, "XPGainedLastWeek": 185 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 316, "UnspentXP": 577, "XPGainedLastRace": 0, "XPGainedLastWeek": 115 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 317, "UnspentXP": 340, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 318, "UnspentXP": 481, "XPGainedLastRace": 0, "XPGainedLastWeek": 185 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 319, "UnspentXP": 424, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 88, "MentalityOpinion": 1, "StaffID": 320, "UnspentXP": 468, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 39, "MentalityOpinion": 2, "StaffID": 321, "UnspentXP": 903, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 55, "MentalityOpinion": 2, "StaffID": 322, "UnspentXP": 325, "XPGainedLastRace": 0, "XPGainedLastWeek": 570 }, { "Mentality": 88, "MentalityOpinion": 1, "StaffID": 333, "UnspentXP": 573, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 49, "MentalityOpinion": 2, "StaffID": 334, "UnspentXP": 703, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 335, "UnspentXP": 730, "XPGainedLastRace": 0, "XPGainedLastWeek": 160 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 337, "UnspentXP": 649, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 338, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 341, "UnspentXP": 43, "XPGainedLastRace": 0, "XPGainedLastWeek": 105 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 343, "UnspentXP": 768, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 344, "UnspentXP": 647, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 345, "UnspentXP": 389, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 361, "UnspentXP": 49, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 364, "UnspentXP": 807, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 365, "UnspentXP": 278, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 366, "UnspentXP": 515, "XPGainedLastRace": 0, "XPGainedLastWeek": 158 }, { "Mentality": 75, "MentalityOpinion": 1, "StaffID": 367, "UnspentXP": 1116, "XPGainedLastRace": 0, "XPGainedLastWeek": 152 }, { "Mentality": 39, "MentalityOpinion": 2, "StaffID": 368, "UnspentXP": 789, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 57, "MentalityOpinion": 2, "StaffID": 369, "UnspentXP": 524, "XPGainedLastRace": 0, "XPGainedLastWeek": 152 }, { "Mentality": 62, "MentalityOpinion": 2, "StaffID": 370, "UnspentXP": 383, "XPGainedLastRace": 0, "XPGainedLastWeek": 152 }, { "Mentality": 38, "MentalityOpinion": 2, "StaffID": 373, "UnspentXP": 212, "XPGainedLastRace": 0, "XPGainedLastWeek": 1080 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 374, "UnspentXP": 742, "XPGainedLastRace": 0, "XPGainedLastWeek": 131 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 375, "UnspentXP": 613, "XPGainedLastRace": 0, "XPGainedLastWeek": 771 }, { "Mentality": 84, "MentalityOpinion": 1, "StaffID": 376, "UnspentXP": 913, "XPGainedLastRace": 0, "XPGainedLastWeek": 630 }, { "Mentality": 47, "MentalityOpinion": 2, "StaffID": 377, "UnspentXP": 873, "XPGainedLastRace": 0, "XPGainedLastWeek": 400 }, { "Mentality": 89, "MentalityOpinion": 1, "StaffID": 378, "UnspentXP": 688, "XPGainedLastRace": 0, "XPGainedLastWeek": 121 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 379, "UnspentXP": 36, "XPGainedLastRace": 0, "XPGainedLastWeek": 648 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 380, "UnspentXP": 54, "XPGainedLastRace": 0, "XPGainedLastWeek": 340 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 381, "UnspentXP": 269, "XPGainedLastRace": 0, "XPGainedLastWeek": 260 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 382, "UnspentXP": 648, "XPGainedLastRace": 0, "XPGainedLastWeek": 260 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 383, "UnspentXP": 617, "XPGainedLastRace": 0, "XPGainedLastWeek": 90 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 384, "UnspentXP": 312, "XPGainedLastRace": 0, "XPGainedLastWeek": 560 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 385, "UnspentXP": 518, "XPGainedLastRace": 0, "XPGainedLastWeek": 380 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 386, "UnspentXP": 127, "XPGainedLastRace": 0, "XPGainedLastWeek": 380 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 387, "UnspentXP": 162, "XPGainedLastRace": 0, "XPGainedLastWeek": 1010 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 388, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 200 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 389, "UnspentXP": 220, "XPGainedLastRace": 0, "XPGainedLastWeek": 400 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 390, "UnspentXP": 972, "XPGainedLastRace": 0, "XPGainedLastWeek": 300 }, { "Mentality": 52, "MentalityOpinion": 2, "StaffID": 391, "UnspentXP": 417, "XPGainedLastRace": 0, "XPGainedLastWeek": 144 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 392, "UnspentXP": 967, "XPGainedLastRace": 0, "XPGainedLastWeek": 147 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 393, "UnspentXP": 557, "XPGainedLastRace": 0, "XPGainedLastWeek": 113 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 394, "UnspentXP": 158, "XPGainedLastRace": 0, "XPGainedLastWeek": 288 }, { "Mentality": 85, "MentalityOpinion": 1, "StaffID": 395, "UnspentXP": 41, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 25, "MentalityOpinion": 3, "StaffID": 397, "UnspentXP": 994, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 47, "MentalityOpinion": 2, "StaffID": 398, "UnspentXP": 712, "XPGainedLastRace": 0, "XPGainedLastWeek": 104 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 399, "UnspentXP": 794, "XPGainedLastRace": 0, "XPGainedLastWeek": 981 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 400, "UnspentXP": 689, "XPGainedLastRace": 0, "XPGainedLastWeek": 257 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 401, "UnspentXP": 857, "XPGainedLastRace": 0, "XPGainedLastWeek": 405 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 402, "UnspentXP": 330, "XPGainedLastRace": 0, "XPGainedLastWeek": 327 }, { "Mentality": 30, "MentalityOpinion": 3, "StaffID": 403, "UnspentXP": 378, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 26, "MentalityOpinion": 3, "StaffID": 404, "UnspentXP": 478, "XPGainedLastRace": 0, "XPGainedLastWeek": 119 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 405, "UnspentXP": 954, "XPGainedLastRace": 0, "XPGainedLastWeek": 420 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 406, "UnspentXP": 649, "XPGainedLastRace": 0, "XPGainedLastWeek": 494 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 407, "UnspentXP": 403, "XPGainedLastRace": 0, "XPGainedLastWeek": 939 }, { "Mentality": 63, "MentalityOpinion": 2, "StaffID": 408, "UnspentXP": 693, "XPGainedLastRace": 0, "XPGainedLastWeek": 128 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 409, "UnspentXP": 645, "XPGainedLastRace": 0, "XPGainedLastWeek": 425 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 410, "UnspentXP": 34, "XPGainedLastRace": 0, "XPGainedLastWeek": 337 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 411, "UnspentXP": 305, "XPGainedLastRace": 0, "XPGainedLastWeek": 1104 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 413, "UnspentXP": 698, "XPGainedLastRace": 0, "XPGainedLastWeek": 238 }, { "Mentality": 21, "MentalityOpinion": 3, "StaffID": 414, "UnspentXP": 238, "XPGainedLastRace": 0, "XPGainedLastWeek": 180 }, { "Mentality": 10, "MentalityOpinion": 3, "StaffID": 415, "UnspentXP": 1043, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 416, "UnspentXP": 877, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 417, "UnspentXP": 824, "XPGainedLastRace": 0, "XPGainedLastWeek": 469 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 418, "UnspentXP": 193, "XPGainedLastRace": 0, "XPGainedLastWeek": 264 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 419, "UnspentXP": 223, "XPGainedLastRace": 0, "XPGainedLastWeek": 446 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 420, "UnspentXP": 441, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 421, "UnspentXP": 75, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 422, "UnspentXP": 852, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 423, "UnspentXP": 798, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 424, "UnspentXP": 625, "XPGainedLastRace": 0, "XPGainedLastWeek": 114 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 425, "UnspentXP": 973, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 426, "UnspentXP": 782, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 427, "UnspentXP": 67, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 428, "UnspentXP": 673, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 429, "UnspentXP": 505, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 430, "UnspentXP": 564, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 431, "UnspentXP": 116, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 432, "UnspentXP": 602, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 433, "UnspentXP": 576, "XPGainedLastRace": 0, "XPGainedLastWeek": 115 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 434, "UnspentXP": 4, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 435, "UnspentXP": 552, "XPGainedLastRace": 0, "XPGainedLastWeek": 165 }, { "Mentality": 42, "MentalityOpinion": 2, "StaffID": 436, "UnspentXP": 254, "XPGainedLastRace": 0, "XPGainedLastWeek": 513 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 437, "UnspentXP": 590, "XPGainedLastRace": 0, "XPGainedLastWeek": 950 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 438, "UnspentXP": 189, "XPGainedLastRace": 0, "XPGainedLastWeek": 594 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 439, "UnspentXP": 651, "XPGainedLastRace": 0, "XPGainedLastWeek": 1010 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 536, "UnspentXP": 244, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 537, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 538, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 539, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 43, "MentalityOpinion": 2, "StaffID": 540, "UnspentXP": 125, "XPGainedLastRace": 0, "XPGainedLastWeek": 125 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 541, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 542, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 543, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 544, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 545, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 546, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 547, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 548, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 549, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 550, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 551, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 554, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 555, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 556, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 557, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 567, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 568, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 600, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 601, "UnspentXP": 240, "XPGainedLastRace": 0, "XPGainedLastWeek": 240 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 602, "UnspentXP": 270, "XPGainedLastRace": 0, "XPGainedLastWeek": 270 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 603, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 72, "MentalityOpinion": 1, "StaffID": 604, "UnspentXP": 612, "XPGainedLastRace": 0, "XPGainedLastWeek": 612 }, { "Mentality": 89, "MentalityOpinion": 1, "StaffID": 605, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 37, "MentalityOpinion": 2, "StaffID": 606, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 607, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 608, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 609, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 610, "UnspentXP": 353, "XPGainedLastRace": 0, "XPGainedLastWeek": 353 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 611, "UnspentXP": 340, "XPGainedLastRace": 0, "XPGainedLastWeek": 340 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 612, "UnspentXP": 300, "XPGainedLastRace": 0, "XPGainedLastWeek": 300 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 613, "UnspentXP": 342, "XPGainedLastRace": 0, "XPGainedLastWeek": 342 }, { "Mentality": 60, "MentalityOpinion": 2, "StaffID": 614, "UnspentXP": 380, "XPGainedLastRace": 0, "XPGainedLastWeek": 380 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 615, "UnspentXP": 270, "XPGainedLastRace": 0, "XPGainedLastWeek": 270 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 616, "UnspentXP": 200, "XPGainedLastRace": 0, "XPGainedLastWeek": 200 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 617, "UnspentXP": 180, "XPGainedLastRace": 0, "XPGainedLastWeek": 180 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 618, "UnspentXP": 240, "XPGainedLastRace": 0, "XPGainedLastWeek": 240 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 619, "UnspentXP": 270, "XPGainedLastRace": 0, "XPGainedLastWeek": 270 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 620, "UnspentXP": 270, "XPGainedLastRace": 0, "XPGainedLastWeek": 270 }, { "Mentality": 50, "MentalityOpinion": 2, "StaffID": 621, "UnspentXP": 260, "XPGainedLastRace": 0, "XPGainedLastWeek": 260 }, { "Mentality": 76, "MentalityOpinion": 1, "StaffID": 622, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 154 }, { "Mentality": 71, "MentalityOpinion": 1, "StaffID": 623, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 154 }, { "Mentality": 30, "MentalityOpinion": 3, "StaffID": 624, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 95 }, { "Mentality": 26, "MentalityOpinion": 3, "StaffID": 625, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 95 }, { "Mentality": 19, "MentalityOpinion": 3, "StaffID": 626, "UnspentXP": 144, "XPGainedLastRace": 0, "XPGainedLastWeek": 144 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 628, "UnspentXP": 126, "XPGainedLastRace": 0, "XPGainedLastWeek": 126 }, { "Mentality": 25, "MentalityOpinion": 3, "StaffID": 630, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 95 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 633, "UnspentXP": 122, "XPGainedLastRace": 0, "XPGainedLastWeek": 122 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 635, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 154 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 636, "UnspentXP": 122, "XPGainedLastRace": 0, "XPGainedLastWeek": 122 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 637, "UnspentXP": 137, "XPGainedLastRace": 0, "XPGainedLastWeek": 137 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 638, "UnspentXP": 126, "XPGainedLastRace": 0, "XPGainedLastWeek": 126 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 639, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 154 }, { "Mentality": 26, "MentalityOpinion": 3, "StaffID": 640, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 95 }, { "Mentality": 45, "MentalityOpinion": 2, "StaffID": 641, "UnspentXP": 95, "XPGainedLastRace": 0, "XPGainedLastWeek": 95 }, { "Mentality": 76, "MentalityOpinion": 1, "StaffID": 642, "UnspentXP": 154, "XPGainedLastRace": 0, "XPGainedLastWeek": 154 }, { "Mentality": 45, "MentalityOpinion": 2, "StaffID": 643, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 45, "MentalityOpinion": 2, "StaffID": 644, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 29, "MentalityOpinion": 3, "StaffID": 645, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 646, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 647, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 648, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 59, "MentalityOpinion": 2, "StaffID": 649, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 650, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 651, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 652, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 653, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 654, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 655, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 656, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 657, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 658, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 659, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 660, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 51, "MentalityOpinion": 2, "StaffID": 661, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 662, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 663, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 54, "MentalityOpinion": 2, "StaffID": 664, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 665, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 51, "MentalityOpinion": 2, "StaffID": 666, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 51, "MentalityOpinion": 2, "StaffID": 667, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 668, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 64, "MentalityOpinion": 2, "StaffID": 669, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 93, "MentalityOpinion": 0, "StaffID": 670, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 76, "MentalityOpinion": 1, "StaffID": 671, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 59, "MentalityOpinion": 2, "StaffID": 672, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 47, "MentalityOpinion": 2, "StaffID": 673, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 77, "MentalityOpinion": 1, "StaffID": 674, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 24, "MentalityOpinion": 3, "StaffID": 677, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 97, "MentalityOpinion": 0, "StaffID": 678, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 24, "MentalityOpinion": 3, "StaffID": 679, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 100, "MentalityOpinion": 0, "StaffID": 680, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 24, "MentalityOpinion": 3, "StaffID": 681, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 24, "MentalityOpinion": 3, "StaffID": 682, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 44, "MentalityOpinion": 2, "StaffID": 683, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 45, "MentalityOpinion": 2, "StaffID": 684, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 685, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 686, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 687, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 688, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 689, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 690, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 691, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 692, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 693, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 694, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 695, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 696, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 697, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 698, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 699, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 700, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 701, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 702, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 703, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 704, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 705, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 706, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 707, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 708, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 709, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 31, "MentalityOpinion": 3, "StaffID": 710, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 711, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 712, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 713, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 714, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 715, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 }, { "Mentality": 36, "MentalityOpinion": 2, "StaffID": 716, "UnspentXP": 0, "XPGainedLastRace": 0, "XPGainedLastWeek": 0 } ] } ================================================ FILE: src/index.html ================================================ Database Editor for F1 Manager
version 3.4.0-dev

DB Editor Online
F1 Manager

Edit your F1 Manager save files directly in your browser. Edit your F1 Manager save files directly in your browser.

Upload your save file

Drop your .sav file here or to select it manually.

Recent
Latest news
Create news
Regenerate news
Data
Comparison ?
Select at leat 2 drivers for H2H!
 
 
 
 
 
 
RACE
QUALIFYING
POINTS
PODIUMS
BEST RACE
BEST QUALI
DNF
Compact
Edit
Hide historic drivers
Show details
Records Hub ?
#
DRIVER
PTS
#
TEAM
PTS
Standings Drivers
6/24
Standings Teams
6/24
Qualifying Analysis
POLES
Q3S
Q2S
Head to head Comparison
RACE
QUALI
Wins Drivers
Wins Teams
Driver of the day Drivers
Podiums Teams
Podiums Drivers
Poles Teams
Current grid ?
Line ups
RED BULL
FERRARI
MCLAREN
MERCEDES
ASTON MARTIN
ALPINE
WILLIAMS
ALPHA TAURI
ALFA ROMEO
HAAS
CUSTOM TEAM
Male
Placeholder 2
Details
Age
25 years old
Retirement
56 years old
Number
81
#1 if WC
Availability
Super License
Retired
Overall
Cornering
Braking
Control
Smoothness
Adaptability
Overtaking
Defence
Reactions
Accuracy
Chasis
Front Wing
Rear Wing
Sidepods
Floor
Suspension
Friendliness
Feedback
Composure
Cooling
Drs
High Speed Performance
Med. Speed Performance
Low Speed Performance
Drag
Airflow
Sensitivity
Training
Aptitude
Leadership
Processes
Other Attributes
Growth
Aggression
Marketability
Personal situation
Positive
Team performance
Positive
Team principal
Positive
Objectives
Season Objective
Long-term Objective
Economy
Balance
$
Cost cap spent
$
Others
Board confidence
Pit crew
Jacks
Wing replace
Car release
Speed
Fatigue
Car build
Wheel gun
Tyres off
Tyres on
Car Development
Factory
50%
Design centre
50%
Wind tunnel
50%
CFD simulator
50%
Suspension simulator
50%
Car part test centre
50%
Operations
Board room
50%
Hospitality centre
50%
Weather centre
50%
Helipad
50%
Memorabilia Room
50%
Tour centre
50%
Staff Facilities
Team hub
50%
Scouting department
50%
Race simulator
50%
There is already a modified calendar or you already did the first race! Try again next season!
Delete race
Calendar ?
Regulations
Sporting regulations
Cost cap
$
Season engine limit
Season ERS limit
Season gearbox limit
Points system
Create point system
Pos
Points
Wind tunnel & CFD
Create resource package
Pos
Wind tunnel
CFD
Add custom engine
Performance ?
1
RED BULL
1
FERRARI
1
MERCEDES
1
ALPINE
1
MCLAREN
1
ALFA ROMEO
1
ASTON MARTIN
1
HAAS
1
ALPHA TAURI
1
WILLIAMS
1
CUSTOM TEAM
1
Red Bull
2
Red Bull
1
Ferrari
2
Ferrari
1
Mclaren
2
Mclaren
1
Mercedes
2
Mercedes
1
Alpine
2
Alpine
1
Williams
2
Williams
1
Haas
2
Haas
1
Alpha Tauri
2
Alpha Tauri
1
Alfa Romeo
2
Alfa Romeo
1
Aston Martin
2
Aston Martin
1
Custom Team
2
Custom Team
Chasis
Drag reduction %
Engine cooling %
Airflow middle %
DRS Delta %
Front wing
Airflow front %
Airflow sensitivity %
Brake cooling %
Low speed downforce kN
Medium speed downforce kN
High speed downforce kN
Rear wing
Airflow sensitivity %
DRS Delta %
Drag reduction %
Low speed downforce kN
Medium speed downforce kN
High speed downforce kN
Sidepods
Airflow front %
Drag reduction %
Engine cooling %
Airflow middle %
Underfloor
Airflow sensitivity %
Drag reduction %
Low speed downforce kN
Medium speed downforce kN
High speed downforce kN
Suspension
Airflow front %
Brake cooling %
Drag reduction %
Low speed downforce kN
Medium speed downforce kN
High speed downforce kN
FERRARI
Power %
Fuel efficiency %
Performance threshold %
Performance loss %
Engine durability %
ERS durability %
Gearbox durability %
RED BULL POWERTRAINS
Power %
Fuel efficiency %
Performance threshold %
Performance loss %
Engine durability %
ERS durability %
Gearbox durability %
MERCEDES
Power %
Fuel efficiency %
Performance threshold %
Performance loss %
Engine durability %
ERS durability %
Gearbox durability %
RENAULT
Power %
Fuel efficiency %
Performance threshold %
Performance loss %
Engine durability %
ERS durability %
Gearbox durability %
Save not compatible with 2025 Season DLC mod
Your save must be from F1 Manager 24, and its date must be before the first race of the 2024 season!
Time travel to end of 2024
05 February 2024
Extra drivers
Missing staff
New F2/F3 drivers
New F1 academy drivers
Driver Line-ups
Lewis -> Ferrari
Carlos -> Williams
A. Kimi -> Mercedes
Driver/staff stats
BORTOLETO 69 OVR
ANTONELLI 71 OVR
PIASTRI 84 OVR
Calendar
Season opener: Bahrain
Sprints: China, Miami, Austria,
COTA, Brazil, Qatar
Regulations
Fastest lap +1 points
CFD Adjusted
RED BULL 4.2h /ATR Preiod
MCLAREN 4.5h /ATR Preiod
FERRARI 4.8h /ATR Preiod
Performance
RED BULL
58.5%
MCLAREN
56.5%
WILLIAMS
46.1%
Save not compatible with 2026 Season Mod
Your save must be from F1 Manager 24 can't have generated staff, and its date must be before the first race of the 2024 season!
Apply all
Time travel to end of 2025
05 February 2026
Extra drivers
Missing staff
New F2/F3 drivers
New F1 academy drivers
Driver Line-ups
PEREZ & BOTTAS
HADJAR
LINDBLAD
Driver/staff stats
HADJAR 75 OVR
ANTONELLI 71 OVR
BEARMAN 77 OVR
Calendar
Season opener: Bahrain
Sprints: China, Miami, Austria,
COTA, Brazil, Qatar
Regulations
Fastest lap +1 points
Cost Cap 139.2 M€
Add 2025 Results
MCLAREN
0 pts
MERCEDES
0 pts
RED BULL
0 pts
CARS & ENGINES
Car performance of
ASTON MARTIN
STAKE SAUBER
ENGINE UPGRADES TPS
Allows engine upgrades turning points
to appear in the news section every
quarter of the season
Support the project
The Database Editor will remain free, and no core features will ever be locked behind a paywall. However, running and maintaining this project takes time and resources. If you found the tool useful and want to support its development,
consider becoming a Patron!
5.5€/month tier:
  • Support the project
  • Extra themes for the editor
  • Ad-free experience (no pop-ups)
  • Access to progress posts
  • Recognition as a supporter
  • Ability to request new features
10.5€/month tier:
  • All the previous tiers rewards, plus:
  • Even more support to the project!
  • Saving your data from +5 seasons
  • Sneak peeks of future development
PATREON PAGE
================================================ FILE: src/index.js ================================================ // Bootstrap import "bootstrap/dist/css/bootstrap.min.css"; import 'bootstrap-icons/font/bootstrap-icons.min.css'; // Resto de imports import interact from 'interactjs'; import Chart from 'chart.js/auto'; import Annotation from "chartjs-plugin-annotation"; import 'chartjs-plugin-annotation'; import 'chartjs-plugin-datalabels'; import './js/frontend/calendar.js'; import './js/frontend/renderer.js'; import './js/frontend/transfers.js'; import './js/frontend/stats.js'; import './js/frontend/performance.js'; import './js/frontend/seasonViewer.js'; import './js/frontend/head2head.js'; import './js/frontend/teams.js'; import './js/frontend/regulations.js'; import './js/frontend/dragFile.js'; import './js/frontend/news.js' import './js/frontend/devTools.js'; import './themes.css' import './styles.css'; ================================================ FILE: src/js/backend/UESaveHandler.js ================================================ import { Gvas, Serializer } from "./UESaveTool"; import pako from "pako"; import { saveAs } from "file-saver"; import { Buffer } from "buffer"; export const parseGvasProps = (Properties) => { const careerSaveMetadata = {}; const metadataProperty = Properties.Properties.filter(x => x.Name === "MetaData")[0]; const careerSaveMetadataProperty = metadataProperty.Properties[0]; careerSaveMetadataProperty.Properties.forEach(prop => { careerSaveMetadata[prop.Name] = prop.Property || prop.Properties; }) return { careerSaveMetadata }; } export const analyzeFileToDatabase = async (file, SQL) => { return new Promise((resolve) => { if (file !== undefined) { let reader = new FileReader(); reader.onload = async (e) => { const serial = new Serializer(Buffer.from(reader.result)); const gvasMeta = new Gvas().deserialize(serial); const { Header, Properties } = gvasMeta; const { SaveGameVersion, EngineVersion } = Header; const { BuildId, Build } = EngineVersion; let version = 0, gameVersion, gameVersionWithBuild; switch (SaveGameVersion) { case 2: version = 2; gameVersion = BuildId.substring(BuildId.indexOf("22_") + 3); gameVersionWithBuild = `${gameVersion}.${Build & 0x7fffffff}`; break; case 3: if (BuildId.indexOf("volta23") !== -1) { version = 3; gameVersion = BuildId.substring(BuildId.indexOf("23+") + 3); gameVersionWithBuild = `${gameVersion}.${Build & 0x7fffffff}`; } if (BuildId.indexOf("volta24") !== -1) { version = 4; gameVersion = BuildId.substring(BuildId.indexOf("24+") + 8); gameVersionWithBuild = `${gameVersion}.${Build & 0x7fffffff}`; } break; default: version = 0; } const unk_zero = serial.readInt32(); const total_size = serial.readInt32(); const size_1 = serial.readInt32(); const size_2 = serial.readInt32(); const size_3 = serial.readInt32(); const compressedData = serial.read(total_size); const output = pako.inflate(compressedData); const databaseFile = output.slice(0, size_1); const text = new TextDecoder().decode(databaseFile.slice(0, 16)); // @ts-ignore const db = new SQL.Database(databaseFile); const metadata = { filename: file.name, // for in-app version, fullBuildId: BuildId, gameVersion, gameVersionWithBuild, databaseFile, gvasMeta, gvasHeader: Header, // read-only ...parseGvasProps(Properties), otherDatabases: [{ size: size_2, file: output.slice(size_1, size_1 + size_2), }, { size: size_3, file: output.slice(size_1 + size_2, size_1 + size_2 + size_3), }] } if (process.env.NODE_ENV === 'development') { // saveAs(new Blob([metadata.chunk0], {type: "application/binary"}), "chunk0"); } resolve({ db, metadata }); }; reader.readAsArrayBuffer(file); } }); } export const repack = (db, metadata, overwrite = false) => { db.exec(` PRAGMA journal_mode = OFF; PRAGMA temp_store = MEMORY; PRAGMA synchronous = OFF; PRAGMA optimize; VACUUM; `); const db_data = db.export(); const db_size = db_data.length; const { otherDatabases, gvasMeta } = metadata; const s1 = otherDatabases[0].size; const s2 = otherDatabases[1].size; const compressedData = new Buffer(db_size + s1 + s2); compressedData.set(db_data, 0); compressedData.set(otherDatabases[0].file, db_size); compressedData.set(otherDatabases[1].file, db_size + s1); const compressed = pako.deflate(compressedData, { level: 9 }); const compressed_size = compressed.length; const serialized = gvasMeta.serialize(); const meta_length = serialized.length; const check = new Gvas().deserialize( new Serializer(Buffer.from(serialized)) ); if (JSON.stringify(gvasMeta) === JSON.stringify(check)) { const finalData = new Buffer(meta_length + 16 + compressed_size); finalData.set(serialized, 0); finalData.writeInt32LE(compressed_size, meta_length); finalData.writeInt32LE(db_size, meta_length + 4); finalData.writeInt32LE(s1, meta_length + 8); finalData.writeInt32LE(s2, meta_length + 12); finalData.set(compressed, meta_length + 16); console.log("Repacked", finalData); return { finalData, metadata }; saveAs(new Blob([finalData], { type: "application/binary" }), metadata.filename); } else { alert("Savefile Serialization Check failed.") } } export const dump = (db, metadata) => { saveAs(new Blob([db.export()], { type: "application/vnd.sqlite3" }), metadata.filename + ".db"); } ================================================ FILE: src/js/backend/UESaveTool/Gvas.js ================================================ import {PropertyFactory} from './factories'; import {GvasHeader} from "./GvasHeader"; import {Tuple} from './properties'; import {SerializationError} from './PropertyErrors'; import {Serializer} from "./Serializer"; import { Buffer } from "buffer"; export class Gvas { constructor() { this.Header = new GvasHeader(); this.Properties = new Tuple(); } get Size() { let size = this.Header.Size; size += this.Properties.Size; size += 4; return size; } deserialize(serial) { let format = serial.read(4); if (Buffer.compare(Buffer.from('GVAS'), format) !== 0) throw Error(`Unexpected header, expected 'GVAS`) this.Header.deserialize(serial); this.Properties.Name = this.Header.SaveGameClassName; this.Properties.deserialize(serial); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.write(this.Header.serialize()); serial.write(this.Properties.serialize()); serial.seek(4); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let gvas = new Gvas(); gvas.Header = GvasHeader.from(obj.Header); gvas.Properties = PropertyFactory.create(obj.Properties); return gvas; } } ================================================ FILE: src/js/backend/UESaveTool/GvasHeader.js ================================================ import {SerializationError} from '.'; import {PropertyFactory} from './factories'; import {Serializer} from './Serializer'; import { Buffer } from "buffer"; export class GvasHeader { constructor() { this.Format = 'GVAS'; this.SaveGameVersion = 0; this.PackageVersion = 0; this.PackageFileVersionUE5 = 0; this.EngineVersion = { Major: 0, Minor: 0, Patch: 0, Build: 0, BuildId: "" } this.CustomFormatVersion = 0; this.CustomFormatData = { Count: 0, Entries: [] } this.SaveGameClassName = ""; } get Size() { let size = this.Format.length; size += 18; size += this.EngineVersion.BuildId.length + 1 + 4; if (this.EngineVersion.Major >= 5) { size += 4; } size += 8; this.CustomFormatData.Entries.forEach(guid => { size += guid.Size; // 20 }) size += this.SaveGameClassName.length + 1 + 4; return size; } deserialize(serial) { /* 5.3: https://github.com/EpicGames/UnrealEngine/blob/5.3/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp#L85 */ // FileTypeTag: GVAS this.SaveGameVersion = serial.readInt32(); this.PackageVersion = serial.readInt32(); if (this.SaveGameVersion >= 3) { this.PackageFileVersionUE5 = serial.readInt32(); /* this needs to be larger than 1000 */ } /* 3 means PackageFileSummaryVersionChange, rather than F1M 2023 https://github.com/EpicGames/UnrealEngine/blob/5.3/Engine/Source/Runtime/Engine/Private/GameplayStatics.cpp#L93 */ this.EngineVersion.Major = serial.readUInt16(); this.EngineVersion.Minor = serial.readUInt16(); this.EngineVersion.Patch = serial.readUInt16(); this.EngineVersion.Build = serial.readUInt32(); this.EngineVersion.BuildId = serial.readString(); this.CustomFormatVersion = serial.readInt32(); this.CustomFormatData.Count = serial.readInt32(); for (let i = 0; i < this.CustomFormatData.Count; i++) { let guid = PropertyFactory.create({ Type: 'Guid' }) this.CustomFormatData.Entries.push(guid.deserialize(serial)); } this.SaveGameClassName = serial.readString(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.write(Buffer.from(this.Format)); serial.writeInt32(this.SaveGameVersion); serial.writeInt32(this.PackageVersion); if (this.SaveGameVersion >= 3) { serial.writeInt32(this.PackageFileVersionUE5); } // UE 5 for F1M 23 serial.writeUInt16(this.EngineVersion.Major); serial.writeUInt16(this.EngineVersion.Minor); serial.writeUInt16(this.EngineVersion.Patch); serial.writeUInt32(this.EngineVersion.Build); serial.writeString(this.EngineVersion.BuildId); serial.writeInt32(this.CustomFormatVersion); serial.writeInt32(this.CustomFormatData.Count); this.CustomFormatData.Entries.forEach(guid => serial.write(guid.serialize())); serial.writeString(this.SaveGameClassName); if (serial.tell != this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let header = new GvasHeader(); header.SaveGameVersion = obj.SaveGameVersion; header.PackageVersion = obj.PackageVersion; header.EngineVersion = obj.EngineVersion; header.CustomFormatVersion = obj.CustomFormatVersion; header.CustomFormatData.Count = obj.CustomFormatData.Count; obj.CustomFormatData.Entries.forEach(guid => { header.CustomFormatData.Entries.push(PropertyFactory.create(guid)); }); header.SaveGameClassName = obj.SaveGameClassName; return header; } } ================================================ FILE: src/js/backend/UESaveTool/LICENSE ================================================ MIT License Copyright © 2021 by Ch1pset Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: src/js/backend/UESaveTool/PropertyErrors.js ================================================ export class SerializationError extends Error { constructor(prop) { super(`Problem occurred during serialization of Property: ${prop.Name}`); } } export class DeserializationError extends Error { constructor(type, offset) { super(`Problem occurred during deserialization of Property '${type}' at offset 0x${offset.toString(16)}`) } } export class TypeNotImplementedError extends Error { constructor(type) { super(`No implementation for Property type: '${type}'`); } } ================================================ FILE: src/js/backend/UESaveTool/Serializer.js ================================================ import { Buffer } from "buffer"; export class Serializer { constructor(buf) { this._data = buf; this._offset = 0; } get Data() { return this._data } get tell() { return this._offset } seek(count) { if(this._offset >= this._data.length) throw new Error(`Reached end of Buffer at offset 0x${this.tell.toString(16)}`); return this._offset += count; } read(count) { return this.Data.slice(this.tell, this.seek(count)); } readInt32() { let int = this.Data.readInt32LE(this.tell); this.seek(4); return int; } readUInt32() { let int = this.Data.readUInt32LE(this.tell); this.seek(4); return int; } readInt64() { let int1 = this.Data.readUInt32LE(this.tell); this.seek(4); let int2 = this.Data.readInt32LE(this.tell); this.seek(4); const val = (BigInt(int2) << 32n) + BigInt(int1); if (val > (1n << 52n)) { return val.toString() } return Number(val); } readInt16() { let int = this.Data.readInt16LE(this.tell); this.seek(2) return int; } readUInt16() { let int = this.Data.readUInt16LE(this.tell); this.seek(2); return int; } readInt8() { let int = this.Data.readInt8(this.tell); this.seek(1); return int; } readUInt8() { let int = this.Data.readUInt8(this.tell); this.seek(1); return int; } readFloat() { let float = this.Data.readFloatLE(this.tell); this.seek(4); return float; } readString() { let length = this.readInt32(); let str = this.read(length - 1).toString('latin1'); this.read(1); return str; } readUnicodeString() { let length = this.readInt32(); if (length < 0) { let str = this.read(-length * 2 - 2).toString('utf16le'); this.read(2); return [str, "utf16le"]; } else { let str = this.read(length - 1).toString('latin1'); this.read(1); return [str, "latin1"]; } } write(buf) { this._offset += buf.copy(this.Data, this.tell); } writeInt64(num) { let bi = BigInt.asIntN(64, BigInt(num)); let high = Number(bi >> 32n); let low = Number(bi & ((1n << 32n) - 1n)); this._offset = this.Data.writeUInt32LE(low, this.tell); this._offset = this.Data.writeInt32LE(high, this.tell); // TODO: this._offset = this.Data.writeBigInt64LE(num, this.tell); } writeUInt32(num) { this._offset = this.Data.writeUInt32LE(num, this.tell); } writeInt32(num) { this._offset = this.Data.writeInt32LE(num, this.tell); } writeUInt16(num) { this._offset = this.Data.writeUInt16LE(num, this.tell); } writeInt16(num) { this._offset = this.Data.writeInt16LE(num, this.tell); } writeUInt8(byte) { this._offset = this.Data.writeUInt8(byte, this.tell); } writeInt8(byte) { this._offset = this.Data.writeInt8(byte, this.tell); } writeFloat(num) { this._offset = this.Data.writeFloatLE(num, this.tell); } writeString(str) { this._offset = this.Data.writeInt32LE(str.length + 1, this.tell); this._offset += this.Data.write(str, this.tell); this._offset = this.Data.writeInt8(0, this.tell); } writeUTF16String(str) { this._offset += this.Data.write(str + "\0", this.tell, "utf16le"); } writeLatin1String(str) { this._offset += this.Data.write(str + "\0", this.tell, "latin1"); } append(buf) { this._data = Buffer.concat([this.Data, buf]); this._offset += buf.length; } static alloc(size) { return new Serializer(Buffer.alloc(size)); } } ================================================ FILE: src/js/backend/UESaveTool/arrays/IntArray.js ================================================ import { Property } from '../properties/' import { PropertyFactory } from '../factories' import { SerializationError } from '..'; import { Serializer } from '../Serializer'; export class IntArray extends Property { constructor() { super(); this.Type = "IntProperty" this.Properties = []; } get Size() { let size = this.Properties.length * 4; this.Properties.forEach((int) => { size += int.Size }); return size; } get Count() { return this.Properties.length; } deserialize(serial, count) { serial.seek(count * 4); for (let i = 1; i < count; i++) { let Name = serial.readString(); let Type = serial.readString(); let Size = serial.readInt32(); let prop = PropertyFactory.create({ Name, Type }); prop.deserialize(serial); this.Properties.push(prop); } return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.seek(this.Count * 4); this.Properties.forEach(int => serial.write(int.serialize())) if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let array = new IntArray(); if (obj.Properties !== undefined) obj.Properties.forEach(int => array.Properties.push(PropertyFactory.create(int))); return array; } } ================================================ FILE: src/js/backend/UESaveTool/arrays/SoftObjectArray.js ================================================ import { Property } from '../properties/' import { SerializationError } from '..'; import { Serializer } from '../Serializer'; export class SoftObjectArray extends Property { constructor() { super(); this.Type = "SoftObjectProperty"; this.Properties = []; } get Size() { let size = 0; this.Properties.forEach((str) => { size += str.length + 1 + 4; size += 4; }); return size; } get Count() { return this.Properties.length; } deserialize(serial, count) { for (let i = 0; i < count; i++) { this.Properties.push(serial.readString()); serial.seek(4); } return this; } serialize() { let serial = Serializer.alloc(this.Size); this.Properties.forEach(str => { serial.writeString(str); serial.seek(4); }); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let array = new SoftObjectArray(); if (obj.Properties !== undefined) array.Properties = obj.Properties; return array; } } ================================================ FILE: src/js/backend/UESaveTool/arrays/StructArray.js ================================================ import { StructProperty } from "../properties/index.js"; import { PropertyFactory } from "../index.js"; export class StructArray extends StructProperty { deserialize(serial, count) { // console.log(`Deserializing ${this.Name} Count: ${count}`) this.Name = serial.readString() this.Type = serial.readString() let Size = serial.readInt32(); serial.seek(4); this.StoredPropertyType = serial.readString(); serial.seek(17); let i = 0; while (i < count) { let Name = this.StoredPropertyType; let Type = 'Tuple'; let prop = PropertyFactory.create({ Name, Type }) prop.deserialize(serial) this.Properties.push(prop); i++; } // console.log(`Done Deserializing ${this.Name} Offset: ${serial.tell}`) return this; } static from(obj) { let struct = new StructArray(); struct.Name = obj.Name; struct.Type = obj.Type; struct.StoredPropertyType = obj.StoredPropertyType; struct.Properties = []; if (obj.Properties !== undefined) obj.Properties.forEach((prop) => struct.Properties.push(PropertyFactory.create(prop))); return struct; } } ================================================ FILE: src/js/backend/UESaveTool/arrays/index.js ================================================ export { IntArray } from './IntArray' export { SoftObjectArray } from './SoftObjectArray' export { StructArray } from '../arrays/StructArray' ================================================ FILE: src/js/backend/UESaveTool/factories.js ================================================ import { TypeNotImplementedError } from './index'; class Factory { constructor() { this.Properties = {} this.Arrays = {} } create(obj) { let type = obj.Type if (this.Properties[type] === undefined) throw new TypeNotImplementedError(type); return this.Properties[type].from(obj); } createArray(obj) { let type = obj.Type if (this.Arrays[type] === undefined) throw new TypeNotImplementedError(type); return this.Arrays[type].from(obj); } } export const PropertyFactory = new Factory(); ================================================ FILE: src/js/backend/UESaveTool/index.js ================================================ import {IntArray, SoftObjectArray, StructArray} from './arrays' import {PropertyFactory} from './factories'; import { ArrayProperty, BoolProperty, EnumProperty, FloatProperty, Guid, Int16Property, Int64Property, Int8Property, IntProperty, UInt32Property, ObjectProperty, SoftObjectProperty, StrProperty, StructProperty, Tuple } from './properties' PropertyFactory.Properties['ArrayProperty'] = ArrayProperty; PropertyFactory.Properties['BoolProperty'] = BoolProperty; PropertyFactory.Properties['EnumProperty'] = EnumProperty; PropertyFactory.Properties['FloatProperty'] = FloatProperty; PropertyFactory.Properties['IntProperty'] = IntProperty; PropertyFactory.Properties['UInt32Property'] = UInt32Property; PropertyFactory.Properties['Int64Property'] = Int64Property; PropertyFactory.Properties['UInt64Property'] = Int64Property; PropertyFactory.Properties['Int8Property'] = Int8Property; PropertyFactory.Properties['Int16Property'] = Int16Property; PropertyFactory.Properties['ObjectProperty'] = ObjectProperty; PropertyFactory.Properties['SoftObjectProperty'] = SoftObjectProperty; PropertyFactory.Properties['StrProperty'] = StrProperty; PropertyFactory.Properties['StructProperty'] = StructProperty; PropertyFactory.Properties['Tuple'] = Tuple; PropertyFactory.Properties['Guid'] = Guid; PropertyFactory.Arrays['IntArray'] = IntArray; PropertyFactory.Arrays['SoftObjectArray'] = SoftObjectArray; PropertyFactory.Arrays['StructProperty'] = StructArray; PropertyFactory.Arrays['IntProperty'] = IntArray; PropertyFactory.Arrays['IntProperty'] = IntArray; PropertyFactory.Arrays['SoftObjectProperty'] = SoftObjectArray; export { PropertyFactory } export { Gvas } from './Gvas' export { GvasHeader } from './GvasHeader' export { Serializer } from './Serializer' export * from './PropertyErrors' export * from './properties' export * from './arrays' ================================================ FILE: src/js/backend/UESaveTool/properties/ArrayProperty.js ================================================ import { Buffer } from 'buffer' import { Property } from './' import { PropertyFactory } from '../factories'; import { SerializationError } from '../' import { Serializer } from '../Serializer'; export class ArrayProperty extends Property { constructor() { super(); this.StoredPropertyType = ""; this.Property = new Property(); } get Size() { let size = 0; size += this.Name.length + 1 + 4; size += this.Type.length + 1 + 4; size += 8; // 4 byte size + 4 byte padding size += this.StoredPropertyType.length + 1 + 4; size += 5; // 1 byte padding + 2 byte int + 2 byte padding size += this.Property.Size; return size; } get HeaderSize() { let size = this.Name.length + 1 + 4; size += this.Type.length + 1 + 4; size += 8; size += this.StoredPropertyType.length + 1 + 4; size += 1; return size; } get ArraySize() { if (this.StoredPropertyType === 'IntProperty') return 12; else return this.Size - this.HeaderSize; } deserialize(serial) { serial.seek(4); this.StoredPropertyType = serial.readString() serial.seek(1); let count = serial.readInt16(); serial.seek(2); this.Property = PropertyFactory.createArray({ Name: this.Name, Type: this.StoredPropertyType }); this.Property.deserialize(serial, count) return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.ArraySize); serial.seek(4); serial.writeString(this.StoredPropertyType); serial.seek(1); serial.writeInt16(this.Property.Count); serial.seek(2); serial.write(this.Property.serialize()); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let array = new ArrayProperty(); array.Name = obj.Name; array.Type = obj.Type; array.StoredPropertyType = obj.StoredPropertyType; if (obj.Property !== undefined) array.Property = PropertyFactory.createArray(obj.Property); return array; } } ================================================ FILE: src/js/backend/UESaveTool/properties/BoolProperty.js ================================================ import { Property } from './' import { Serializer } from '..'; import { SerializationError } from '../PropertyErrors'; export class BoolProperty extends Property { constructor() { super(); this.Property = false; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 10; } deserialize(serial) { serial.seek(4); this.Property = serial.readUInt8() === 1; serial.seek(1); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.seek(8); serial.writeUInt8(this.Property === true ? 1 : 0); serial.seek(1); if (serial.tell !== this.Size) throw new SerializationError(this) return serial.Data; } static from(obj) { let prop = new BoolProperty(); obj.Property = !!obj.Property; Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/EnumProperty.js ================================================ import { Serializer } from '../Serializer'; import { SerializationError } from '../PropertyErrors'; import { Property } from '.'; export class EnumProperty extends Property { constructor() { super(); this.EnumType = ""; this.Property = ""; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + this.Property.length + 1 + 4 + this.EnumType.length + 1 + 4 + 9; } deserialize(serial) { serial.seek(4); this.EnumType = serial.readString(); serial.seek(1); this.Property = serial.readString(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.Property.length + 1 + 4); serial.seek(4); serial.writeString(this.EnumType); serial.seek(1); serial.writeString(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new EnumProperty(); Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/FloatProperty.js ================================================ import { Serializer } from '../Serializer'; import { SerializationError } from '../PropertyErrors'; import { Property } from './' export class FloatProperty extends Property { constructor() { super(); this.Property = 0; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 13; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readFloat(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeFloat(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new FloatProperty(); obj.Property = Number(obj.Property); Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Guid.js ================================================ import { Buffer } from 'buffer' import { Property } from './' import { SerializationError } from '..'; import { Serializer } from '../Serializer'; export class Guid extends Property { constructor() { super(); this.Type = 'Guid'; this.Id = "00000000-00-00-00-000000000000"; this.Value = 0; } get Size() { return 20; } deserialize(serial) { this.Id = `${serial.read(4).swap32().toString('hex')}` this.Id += `-${serial.read(2).swap16().toString('hex')}` this.Id += `-${serial.read(2).swap16().toString('hex')}` this.Id += `-${serial.read(2).toString('hex')}` this.Id += `-${serial.read(6).toString('hex')}` this.Value = serial.readInt32(); return this; } serialize() { let guid = this.Id.split('-'); let serial = Serializer.alloc(this.Size); serial.write(Buffer.from(guid[0], 'hex').swap32()); serial.write(Buffer.from(guid[1], 'hex').swap16()); serial.write(Buffer.from(guid[2], 'hex').swap16()); serial.write(Buffer.from(guid[3], 'hex')); serial.write(Buffer.from(guid[4], 'hex')); serial.writeInt32(this.Value); if (serial.tell !== 20) throw new SerializationError(this); return serial.Data; } static from(obj) { let guid = new Guid(); guid.Id = obj.Id; guid.Value = obj.Value; return guid; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Int16Property.js ================================================ import { Property } from './' import { Serializer } from '..'; import { SerializationError } from '../PropertyErrors'; export class Int16Property extends Property { constructor() { super(); this.Property = 0; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 9 + 2; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readInt16(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeInt16(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new Int16Property(); obj.Property = obj.Property || 0; const lb = -1 << 15; const ub = (1 << 15) - 1; if (obj.Property > ub || obj.Property < lb) { throw Error(`${obj.Name} = ${obj.Property} out of range [${lb}, ${ub}]`) } Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Int64Property.js ================================================ import { Property } from './' import { Serializer } from '..'; import { SerializationError } from '../PropertyErrors'; export class Int64Property extends Property { constructor() { super(); this.Property = 0n; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 9 + 8; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readInt64(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeInt64(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new Int64Property(); let bi = 0n; try { bi = BigInt(obj.Property || 0); const lb = -1n << 64n; const ub = (1n << 63n) - 1n; if (bi > ub || bi < lb) { throw Error(`out of range [${lb}, ${ub}]`) } const doubleVal = Number(obj.Property); if (Math.abs(doubleVal) >= (2 ** 53)) { obj.Property = BigInt(obj.Property).toString(); } else { obj.Property = Number(obj.Property); } } catch (e) { throw Error(`${obj.Name} = ${obj.Property}: ${e.toString()}`) } Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Int8Property.js ================================================ import { Property } from './' import { Serializer } from '..'; import { SerializationError } from '../PropertyErrors'; export class Int8Property extends Property { constructor() { super(); this.Property = 0; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 9 + 1; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readInt8(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeInt8(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new Int8Property(); obj.Property = obj.Property || 0; const lb = -128; const ub = 127; if (obj.Property > ub || obj.Property < lb) { throw Error(`${obj.Name} = ${obj.Property} out of range [${lb}, ${ub}]`) } Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/IntProperty.js ================================================ import { Property } from './' import { Serializer } from '..'; import { SerializationError } from '../PropertyErrors'; export class IntProperty extends Property { constructor() { super(); this.Property = 0; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 9 + 4; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readInt32(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeInt32(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new IntProperty(); obj.Property = obj.Property || 0; const lb = -1 << 31; const ub = 0x7fffffff; if (obj.Property > ub || obj.Property < lb) { throw Error(`${obj.Name} = ${obj.Property} out of range [${lb}, ${ub}]`) } Object.assign(prop, obj); return prop; } } export class UInt32Property extends Property { constructor() { super(); this.Property = 0; this.Index = 0; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + 9 + 4; } deserialize(serial) { this.Index = serial.readInt32(); serial.seek(1); this.Property = serial.readUInt32(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(4); serial.writeInt32(this.Index); serial.seek(1); serial.writeUInt32(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new IntProperty(); obj.Property = obj.Property || 0; const lb = 0; const ub = 0xffffffff; if (obj.Property > ub || obj.Property < lb) { throw Error(`${obj.Name} = ${obj.Property} out of range [${lb}, ${ub}]`) } Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/ObjectProperty.js ================================================ import { Serializer } from '../Serializer'; import { SerializationError } from '../PropertyErrors'; import { Property } from './' export class ObjectProperty extends Property { constructor() { super(); this.Property = ""; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + this.Property.length + 1 + 4 + 9; } deserialize(serial) { serial.seek(5); this.Property = serial.readString(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.Property.length + 1 + 4); serial.seek(5); serial.writeString(this.Property); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new ObjectProperty(); Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Property.js ================================================ import { Serializer } from '../' export class Property { constructor() { this.Name = ""; this.Type = ""; } /** * Per-property byte size getter * @returns {Number} `Size` in bytes of all attributes and properties held by this property to be serialized */ get Size() { throw new Error(`Size getter not implemented for property: ${this.Type}`); } /** * Per-property deserialization function * @param {Serializer} serial Serializer instance used to read a buffer * @param {Number} size Size in bytes or Count of elements for Arrays * @returns {Property} Returns `this` instance */ deserialize(serial, size) { throw new Error(`Deserialization not implemented for property: ${this.Type}`); } /** * Per-property serialization function * @returns {Buffer} Returns a `Buffer` of the serialized data */ serialize() { throw new Error(`Serialization not implemented for property: ${this.Type}`); } /** * Factory function for a `Property` type. This should instantiate a `new Property` with default values if not given in the `json` * @param {Object} json Template from which to create a new instance of a `Property` */ static from(json) { throw new Error(`from() not implemented for property: ${this.Type}`); } } ================================================ FILE: src/js/backend/UESaveTool/properties/SoftObjectProperty.js ================================================ import { SerializationError } from '../PropertyErrors'; import { Property } from './' import { Serializer } from '../Serializer'; export class SoftObjectProperty extends Property { constructor() { super(); this.Property = ""; } get Size() { return this.Name.length + 1 + 4 + this.Type.length + 1 + 4 + this.Property.length + 1 + 4 + 13; } deserialize(serial) { serial.seek(5); this.Property = serial.readString(); serial.seek(4); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.Property.length + 8); serial.seek(5); serial.writeString(this.Property); serial.seek(4); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new SoftObjectProperty(); Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/StrProperty.js ================================================ import {SerializationError} from "../PropertyErrors"; import {Serializer} from '../Serializer'; import {Property} from './' import { Buffer } from "buffer"; const is8Bit = string => /^[\x00-\xFF]*$/.test(string); export class StrProperty extends Property { constructor() { super(); this.Property = ""; } get Encoding() { return is8Bit(this.Property) ? "latin1" : "utf16le"; } get StringEncodedLength() { return Buffer.from(this.Property + "\0", this.Encoding).length; } get Size() { const baseLength = this.Name.length + 1 + 4 + this.Type.length + 1 + 4; return baseLength + this.StringEncodedLength + 4 + 9; } deserialize(serial) { serial.seek(5); [this.Property, ] = serial.readUnicodeString(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.StringEncodedLength + 4); serial.seek(5); switch (this.Encoding) { case "latin1": serial.writeInt32(this.StringEncodedLength); serial.writeLatin1String(this.Property); break; case "utf16le": serial.writeInt32(-(this.StringEncodedLength / 2)); serial.writeUTF16String(this.Property); break; } if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new StrProperty(); Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/StrProperty_.js ================================================ import {SerializationError} from "../PropertyErrors"; import {Serializer} from '../Serializer'; import {Property} from './' const is8Bit = string => /^[\x00-\xFF]*$/.test(string); export class StrProperty extends Property { constructor() { super(); this.Property = ""; this.Encoding = "latin1"; } get Encoding() { return is8Bit(this.Property) ? "latin1" : "utf16le"; } get StringEncodedLength() { return Buffer.from(this.Property + "\0", this.Encoding).length; } get Size() { const baseLength = this.Name.length + 1 + 4 + this.Type.length + 1 + 4; return baseLength + this.StringEncodedLength + 4 + 9; } deserialize(serial) { serial.seek(5); [this.Property, this.Encoding] = serial.readUnicodeString(); return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.StringEncodedLength + 4); serial.seek(5); switch (this.Encoding) { case "latin1": serial.writeInt32(this.StringEncodedLength); serial.writeLatin1String(this.Property); break; case "utf16le": serial.writeInt32(-(this.StringEncodedLength / 2)); serial.writeUTF16String(this.Property); break; } if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let prop = new StrProperty(); if (obj.Encoding === "utf8") { obj.Encoding = "latin1"; console.warn("utf8 should be latin1"); } if (obj.Encoding === "latin1") { if (!is8Bit(obj.Property)) { throw Error(`${obj.Name} = ${obj.Property} is outside latin1. consider using utf16le?`) } } else if (obj.Encoding === "utf16le") { } else if (!obj.Encoding) { } else { throw Error(`${obj.Name}: ${obj.Encoding} is unsupported. valid options are: [latin1, utf16le]`) } Object.assign(prop, obj); return prop; } } ================================================ FILE: src/js/backend/UESaveTool/properties/StructProperty.js ================================================ import { Property } from './' import { PropertyFactory } from '../factories'; import { Serializer } from '../Serializer'; export class StructProperty extends Property { constructor() { super(); this.StoredPropertyType = ""; this.Properties = []; } get Size() { let size = this.Name.length + 1 + 4; size += this.Type.length + 1 + 4; size += 8; // 4 byte size + 4 byte padding size += this.StoredPropertyType.length + 1 + 4; size += 17; // 17 byte padding for (let i = 0; i < this.Properties.length; i++) { size += this.Properties[i].Size; } return size; } get HeaderSize() { let size = this.Name.length + 1 + 4; size += this.Type.length + 1 + 4; size += 8; size += this.StoredPropertyType.length + 1 + 4; size += 17; return size } get Count() { return this.Properties.length; } deserialize(serial, size) { // console.log(`Deserializing ${this.Name} Size: ${size}`) serial.seek(4); this.StoredPropertyType = serial.readString(); serial.seek(17); let end = serial.tell + size; let i = 0; while (serial.tell < end) { let Name = this.StoredPropertyType; let Type = 'Tuple'; let prop = PropertyFactory.create({ Name, Type }) prop.deserialize(serial) this.Properties.push(prop); i++; } // console.log(`Done Deserializing ${this.Name} Offset: ${serial.tell}`) return this; } serialize() { let serial = Serializer.alloc(this.Size); serial.writeString(this.Name); serial.writeString(this.Type); serial.writeInt32(this.Size - this.HeaderSize); serial.seek(4); serial.writeString(this.StoredPropertyType); serial.seek(17); for (let i = 0; i < this.Properties.length; i++) { serial.write(this.Properties[i].serialize()); } if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let struct = new StructProperty(); struct.Name = obj.Name; struct.Type = obj.Type; struct.StoredPropertyType = obj.StoredPropertyType; struct.Properties = []; if (obj.Properties !== undefined) obj.Properties.forEach((prop) => struct.Properties.push(PropertyFactory.create(prop))); return struct; } } ================================================ FILE: src/js/backend/UESaveTool/properties/Tuple.js ================================================ import { Property } from './' import { PropertyFactory } from '../factories'; import { SerializationError } from '..'; import { Serializer } from '../Serializer'; export class Tuple extends Property { constructor() { super(); this.Type = 'Tuple'; this.Properties = []; } get Size() { let size = 0; for (let i = 0; i < this.Properties.length; i++) { size += this.Properties[i].Size; } size += 9; return size; } get Count() { return this.Properties.length; } deserialize(serial) { let Name; while ((Name = serial.readString()) !== 'None') { let Type = serial.readString(); let Size = serial.readInt32(); let prop = PropertyFactory.create({ Name, Type }); prop.deserialize(serial, Size); this.Properties.push(prop); } return this; } serialize() { let serial = Serializer.alloc(this.Size); for (let i = 0; i < this.Properties.length; i++) { serial.write(this.Properties[i].serialize()); } serial.writeString('None'); if (serial.tell !== this.Size) throw new SerializationError(this); return serial.Data; } static from(obj) { let tuple = new Tuple(); tuple.Name = obj.Name; if (obj.Properties !== undefined) obj.Properties.forEach(prop => tuple.Properties.push(PropertyFactory.create(prop))); return tuple; } } ================================================ FILE: src/js/backend/UESaveTool/properties/index.js ================================================ export { Property } from './Property' export { BoolProperty } from './BoolProperty' export { IntProperty } from './IntProperty' export { Int8Property } from './Int8Property' export { Int16Property } from './Int16Property' export { UInt32Property } from './IntProperty' export { Int64Property } from './Int64Property' export { FloatProperty } from './FloatProperty' export { StrProperty } from './StrProperty' export { ObjectProperty } from './ObjectProperty' export { SoftObjectProperty } from './SoftObjectProperty' export { StructProperty } from './StructProperty' export { ArrayProperty } from './ArrayProperty' export { EnumProperty } from './EnumProperty' export { Tuple } from './Tuple' export { Guid } from './Guid' ================================================ FILE: src/js/backend/command.js ================================================ // Command.js import { updateFront } from "../frontend/renderer"; import { dbWorker } from "../frontend/dragFile"; import { teamReplaceDict, prettyNames, getGlobals, setGlobals } from "./commandGlobals"; export class Command { /** * @param {string} commandName - Name of the command to execute. * @param {Object} data - Data to send to the worker. */ constructor(commandName, data) { this.commandName = commandName; this.data = data; } async execute() { console.log(`[Command] Executing command: ${this.commandName}`); console.log(`[Command] Data:`, this.data); dbWorker.postMessage({ command: this.commandName, data: this.data }); dbWorker.onmessage = async (msg) => { const response = msg.data; if (response.error) { console.error(`[${this.commandName}] Error:`, response.error); document.querySelector(".error").classList.remove("d-none"); } else { console.log(`[${this.commandName}] Response:`, response.responseMessage); await updateFront(response); if (this.commandName === "saveSelected") { if (response.responseMessage === "Game Year") { this.updateTeamsFor24(response.content[0]); this.addTeam("Custom Team", response.content[1]); } } } }; } promiseExecute() { return new Promise((resolve, reject) => { const handler = (e) => { const resp = e.data; if (resp.command && resp.command !== this.commandName) return; dbWorker.removeEventListener("message", handler); if (resp.error) return reject(resp.error); resolve(resp); }; dbWorker.addEventListener("message", handler); dbWorker.postMessage({ command: this.commandName, data: this.data }); }); } async updateTeamsFor24(year) { if (year === "24") { const data = { teams: { alphatauri: "visarb", alpine: "alpine", alfa: "stake" } } this.replaceTeam("Alpha Tauri", data.teams.alphatauri); this.replaceTeam("Alpine", data.teams.alpine); this.replaceTeam("Alfa Romeo", data.teams.alfa); const yearResponse = { responseMessage: "24 Year", content: data }; await updateFront(yearResponse); } } replaceTeam(originalTeam, newTeam) { teamReplaceDict[originalTeam] = prettyNames[newTeam] || newTeam; } addTeam(originalTeam, newTeam) { teamReplaceDict[originalTeam] = newTeam; } } ================================================ FILE: src/js/backend/commandGlobals.js ================================================ // commandGlobals.js export const teamReplaceDict = { "Alpha Tauri": "Alpha Tauri", "Alpine": "Alpine", "Alfa Romeo": "Alfa Romeo", "Aston Martin": "Aston Martin", "Ferrari": "Ferrari", "Haas": "Haas", "McLaren": "McLaren", "Mercedes": "Mercedes", "Red Bull": "Red Bull", "Williams": "Williams", "Renault": "Renault", "F2": "Formula 2", "F3": "Formula 3", "Custom Team": "Custom Team" }; export const prettyNames = { "visarb": "Visa Cashapp RB", "toyota": "Toyota", "hugo": "Hugo Boss", "alphatauri": "Alpha Tauri", "brawn": "Brawn GP", "porsche": "Porsche", "alpine": "Alpine", "renault": "Renault", "andretti": "Andretti", "lotus": "Lotus", "cadillac": "Cadillac", "alfa": "Alfa Romeo", "audi": "Audi", "sauber": "Sauber", "stake": "Stake Sauber", "redbull": "Red Bull", "ford": "Ford", "aston": "Aston Martin", "racingpoint": "Racing Point", "jordan": "Jordan" }; let path = null; let yearIteration = null; let isCreateATeam = false; let currentDate = null; export function setGlobals({dbPath, year, createTeam, date }) { path = dbPath || path; yearIteration = year || yearIteration; isCreateATeam = createTeam || isCreateATeam; currentDate = date || currentDate; } export function getGlobals() { return { path, yearIteration, isCreateATeam, currentDate }; } ================================================ FILE: src/js/backend/dbManager.js ================================================ let db = null; let metadata = null; export function setDatabase(database, meta) { db = database; metadata = meta; } export function getDatabase() { return db; } export function getMetadata() { return metadata; } export function setMetaData(meta) { metadata = meta; } /** * Ejecuta una consulta SQL y devuelve el resultado según 'type'. * @param {string} query - La consulta a ejecutar. * @param {Array} [params=[]] - Los parámetros para la consulta. * @param {"singleValue"|"singleRow"|"allRows"|"run"} [type="allRows"] - El tipo de resultado. * @returns {any} * - 'singleValue': un único valor (o null). * - 'singleRow': la primera fila (array de valores) o null. * - 'allRows': array de filas (cada fila, array de valores), o [] si no hay ninguna. * - 'run': devuelve true si se ejecutó correctamente. */ export function queryDB(query, params = [], type = 'allRows') { try { const sanitizedParams = Array.isArray(params) ? params.map(p => (Array.isArray(p) && p.length === 1 ? p[0] : p)) : params; if (type === 'exec') { db.exec(query); return true; } if (type === 'run') { db.run(query, sanitizedParams); return true; } const stmt = db.prepare(query); stmt.bind(sanitizedParams); let result = null; if (type === 'singleValue') { if (stmt.step()) { const row = stmt.get(); result = row[0] ?? null; } else { result = null; } } else if (type === 'singleRow') { if (stmt.step()) { result = stmt.get(); } else { result = null; } } else { // allRows result = []; while (stmt.step()) { result.push(stmt.get()); } } stmt.free(); return result; } catch (err) { // 💥 AQUÍ cazamos errores de SQL.js, incluyendo binds mal pasados const fullErr = new Error( `SQL ERROR\nQuery: ${query}\nParams: ${JSON.stringify(params)}\nOriginal: ${err.message}` ); console.error(fullErr); throw fullErr; } } ================================================ FILE: src/js/backend/scriptUtils/calendarUtils.js ================================================ import { queryDB } from "../dbManager"; const weatherDict = { "0": 1, "1": 2, "2": 4, "3": 8, "4": 16, "5": 32 }; export function fetchCalendar() { const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!daySeason) { console.warn("No data found in Player_State."); return []; } const currentSeason = daySeason[1]; const calendarRows = queryDB(` SELECT r.TrackID, r.WeatherStatePractice, r.WeatherStateQualifying, r.WeatherStateRace, r.WeekendType, r.State, t.isF2Race, t.IsF3Race AS isF3Race FROM Races r LEFT JOIN Races_Tracks t ON r.TrackID = t.TrackID WHERE r.SeasonID = ? `, [currentSeason], 'allRows') || []; return calendarRows.map((row) => ({ trackId: row[0], weatherStatePractice: row[1], weatherStateQualifying: row[2], weatherStateRace: row[3], weekendType: row[4], state: row[5], isF2Race: row[6] ?? 0, isF3Race: row[7] ?? 0, })); } export function editCalendar(year_iteration, racesData) { const yearIteration = year_iteration; let maxRaces; let weeks; if (yearIteration === "24") { maxRaces = 24; weeks = [11, 8, 15, 36, 24, 20, 22, 25, 26, 9, 28, 29, 34, 37, 13, 42, 41, 43, 48, 17, 33, 19, 46, 47]; } else if (yearIteration === "23") { maxRaces = 23; weeks = [12, 8, 16, 21, 20, 23, 25, 26, 10, 28, 29, 34, 36, 37, 42, 41, 43, 46, 17, 33, 19, 45, 39]; } else { maxRaces = 0; weeks = []; } const raceBlanks = maxRaces - racesData.length; const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); let actualCalendar = queryDB(` SELECT TrackID FROM Races WHERE SeasonID = ? `, [daySeason[1]], 'allRows') || []; actualCalendar = actualCalendar.map(row => row[0]); //build newCalendar with trackId from each element from the array racesData const newCalendar = racesData.map(race => parseInt(race.trackId)); if (arraysEqual(actualCalendar, newCalendar)) { const ids = queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? `, [daySeason[1]], 'allRows') || []; const raceIDs = ids.map(row => row[0]); for (let i = 0; i < racesData.length; i++) { const race = racesData[i]; const state = race.state; const format = race.type; const rainR = weatherDict[race.rainRace]; const rainRBool = (parseFloat(rainR) >= 8) ? 1 : 0; const rainQ = weatherDict[race.rainQuali]; const rainQBool = (parseFloat(rainQ) >= 8) ? 1 : 0; const rainP = weatherDict[race.rainPractice]; const rainPBool = (parseFloat(rainP) >= 8) ? 1 : 0; const isF2Race = parseInt(race.isF2Race, 10) || 0; const isF3Race = parseInt(race.isF3Race, 10) || 0; const trackId = parseInt(race.trackId, 10); // race_code = race.slice(0, -5); // en Python, no lo usas aquí para nada queryDB(` UPDATE Races SET RainPractice = ?, WeatherStatePractice = ?, RainQualifying = ?, WeatherStateQualifying = ?, RainRace = ?, WeatherStateRace = ?, WeekendType = ? WHERE RaceID = ? `, [rainPBool, rainP, rainQBool, rainQ, rainRBool, rainR, format, raceIDs[i]], 'run'); queryDB(` UPDATE Races_Tracks SET isF2Race = ?, IsF3Race = ? WHERE TrackID = ? `, [isF2Race, isF3Race, trackId], 'run'); } } else { const randomBlanks = []; for (let i = 0; i < raceBlanks; i++) { let n = Math.floor(Math.random() * maxRaces); while (randomBlanks.includes(n)) { n = Math.floor(Math.random() * maxRaces); } randomBlanks.push(n); } for (const el of randomBlanks) { weeks[el] = 0; } weeks = weeks.filter(x => x !== 0); weeks.sort((a, b) => a - b); let leapYearCount = 2; const yearDiff = daySeason[1] - 2023; leapYearCount += yearDiff; let dayStart = 44927 + (yearDiff * 365) + Math.floor(leapYearCount / 4); const dayOfWeek = dayStart % 7; const daysUntilSunday = (8 - dayOfWeek) % 7; dayStart += daysUntilSunday; const lastRaceLastSeason = queryDB(` SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? `, [daySeason[1] - 1], 'singleValue'); const firstRaceThisSeason = queryDB(` SELECT MIN(RaceID) FROM Races WHERE SeasonID = ? `, [daySeason[1]], 'singleValue'); let raceid; if (parseInt(lastRaceLastSeason, 10) === (parseInt(firstRaceThisSeason, 10) - 1)) { raceid = lastRaceLastSeason; } else { raceid = firstRaceThisSeason - 1; } queryDB(` DELETE FROM Races WHERE State != 2 AND SeasonID = ? `, [daySeason[1]], 'run'); for (let i = 0; i < racesData.length; i++) { const race = racesData[i]; const state = race.state; const format = race.type; const rainR = weatherDict[race.rainRace]; const rainRBool = (parseFloat(rainR) >= 8) ? 1 : 0; const rainQ = weatherDict[race.rainQuali]; const rainQBool = (parseFloat(rainQ) >= 8) ? 1 : 0; const rainP = weatherDict[race.rainPractice]; const rainPBool = (parseFloat(rainP) >= 8) ? 1 : 0; const raceCode = parseInt(race.trackId); const isF2Race = parseInt(race.isF2Race, 10) || 0; const isF3Race = parseInt(race.isF3Race, 10) || 0; const temps = queryDB(` SELECT TemperatureMin, TemperatureMax FROM Races_Templates WHERE TrackID = ? `, [raceCode], 'singleRow'); const tempP = randomInt(temps[0], temps[1]); const tempQ = randomInt(temps[0], temps[1]); const tempR = randomInt(temps[0], temps[1]); const day = ((weeks[i] + 1) * 7) + dayStart; raceid += 1; if (state !== "2") { queryDB(` INSERT INTO Races VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) `, [ raceid, daySeason[1], day, raceCode, state, rainPBool, tempP, rainP, rainQBool, tempQ, rainQ, rainRBool, tempR, rainR, format ], 'run'); } queryDB(` UPDATE Races_Tracks SET isF2Race = ?, IsF3Race = ? WHERE TrackID = ? `, [isF2Race, isF3Race, raceCode], 'run'); } } } // Helpers function arraysEqual(a, b) { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) { if (a[i] !== b[i]) return false; } return true; } function randomInt(min, max) { const mn = parseInt(min, 10); const mx = parseInt(max, 10); return Math.floor(Math.random() * (mx - mn + 1)) + mn; } ================================================ FILE: src/js/backend/scriptUtils/carAnalysisUtils.js ================================================ import * as carConstants from './carConstants.js'; import { queryDB } from '../dbManager.js'; import { manage_engine_change } from './editTeamUtils.js'; /** * Devuelve las mejores piezas para cada equipo. * @param {boolean} customTeam - si es true, incluye el equipo 32 además de 1..10 */ export function getBestParts(customTeam = false) { const teams = {}; // Creamos la lista de equipos const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) // 1..10 y 32 : [...Array(10).keys()].map(i => i + 1); // 1..10 for (const teamId of teamList) { teams[teamId] = getPartsFromTeam(teamId); } return teams; } /** * Obtiene TODAS las piezas (varias designs) de un equipo * (Como en Python: get_all_parts_from_team) */ export function getAllPartsFromTeam(teamId) { // Obtenemos Day y Season const [day, currentSeason] = queryDB( "SELECT Day, CurrentSeason FROM Player_State", [], "singleRow" ) || [0, 0]; const partsDict = {}; // Ej. en Python, PartType iba de 3..8 for (let j = 3; j < 9; j++) { const sql = ` SELECT d.DesignID, d.DayCreated, d.DayCompleted, ( SELECT r.TrackID FROM Races r WHERE r.Day >= d.DayCompleted ORDER BY r.Day ASC LIMIT 1 ) AS TrackID FROM Parts_Designs d WHERE d.PartType = ? AND d.TeamID = ? AND d.ValidFrom = ? AND d.DayCompleted > 0 `; let designs = queryDB(sql, [j, teamId, currentSeason], "allRows"); // Para cada design, agregamos info extra: equipped_1, equipped_2, n_parts designs = designs.map(designRow => { // designRow => [ DesignID, DayCreated, DayCompleted, TrackID ] const [designID, dayCreated, dayCompleted, trackID] = designRow; // Vemos si está equipado en loadout 1 const equipped1 = queryDB(` SELECT DesignID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = 1 `, [teamId, j], "singleValue"); let eq1 = (equipped1 === designID) ? 1 : 0; // Equipado en loadout 2? const equipped2 = queryDB(` SELECT DesignID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = 2 `, [teamId, j], "singleValue"); let eq2 = (equipped2 === designID) ? 1 : 0; // Número de partes (items) construidas const nParts = queryDB(` SELECT COUNT(*) FROM Parts_Items WHERE DesignID = ? AND BuildWork = ? `, [designID, carConstants.standardBuildworkPerPart[j]], "singleValue") || 0; // Devolvemos un nuevo array con toda la info return [ designID, // 0 dayCreated, // 1 dayCompleted, // 2 trackID, // 3 eq1, // 4 eq2, // 5 nParts // 6 ]; }); // Asignamos a partsDict[ parts[j] ] = designs // Asumiendo que 'parts[j]' existe. Ajusta si es distinto partsDict[carConstants.parts[j]] = designs; } return partsDict; } /** * Obtiene las piezas "mejores" (MAX(DesignID)) para un equipo y su season actual * (Similar a get_parts_from_team en el Python original) */ export function getPartsFromTeam(teamId) { // Day, Season const [day, season] = queryDB( "SELECT Day, CurrentSeason FROM Player_State", [], "singleRow" ) || [0, 0]; const designs = {}; // En Python, j va de 3..8 => motor = 0 for (let j = 3; j < 9; j++) { const row = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? AND ValidFrom = ? AND (DayCompleted > 0 OR DayCreated < 0) `, [j, teamId, season], "allRows"); designs[j] = row; } // engine: const engine = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = 0 AND TeamID = ? `, [teamId], "allRows"); designs[0] = engine; return designs; } /** * Obtiene las mejores piezas hasta un día concreto (versión con day param) * (Similar a get_best_parts_until en el Python original) */ export function getBestPartsUntil(day, customTeam = false) { // Day, season const [dayCur, season] = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], "singleRow") || [0, 0]; const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); const teams = {}; for (const t of teamList) { const designs = {}; for (let j = 3; j < 9; j++) { const row = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? AND ValidFrom = ? AND ((DayCompleted > 0 AND DayCompleted <= ?) OR DayCreated < 0) `, [j, t, season, day], "allRows"); designs[j] = row; } // engine const engine = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = 0 AND TeamID = ? `, [t], "allRows"); designs[0] = engine; teams[t] = designs; } return teams; } /** * Devuelve un diccionario con los valores de stats (PartStat -> Value) * de cada parte (partType). * (get_car_stats en el Python original) */ export function getCarStats(designDict) { const statsValues = {}; for (const part in designDict) { const designInfo = designDict[part][0]; const designID = (designInfo && designInfo.length) ? designInfo[0] : null; if (designID !== null) { const rows = queryDB(` SELECT PartStat, Value FROM Parts_Designs_StatValues WHERE DesignID = ? `, [designID], "allRows"); // rows => [ [PartStat, Value], [PartStat, Value], ... ] const tmp = {}; for (const [stat, val] of rows) { tmp[stat] = Math.round(val * 1000) / 1000; // round to 3 decimals } statsValues[part] = tmp; } else { const zeroStats = {}; for (const stat of carConstants.defaultPartsStats[part]) { zeroStats[stat] = 0; } statsValues[part] = zeroStats; } } return statsValues; } export function getTyreDegStats(designDict) { const statsValues = {}; //only part 4 and 8 const tyreDegDict = {4: designDict[4], 8: designDict[8]}; for (const part in tyreDegDict) { const designInfo = tyreDegDict[part][0]; const designID = (designInfo && designInfo.length) ? designInfo[0] : null; if (designID !== null) { const rows = queryDB(` SELECT PartStat, Value FROM Parts_Designs_StatValues WHERE DesignID = ? `, [designID], "allRows"); // rows => [ [PartStat, Value], [PartStat, Value], ... ] const tmp = {}; for (const [stat, val] of rows) { tmp[stat] = Math.round(val * 1000) / 1000; // round to 3 decimals } statsValues[part] = tmp; } else { const zeroStats = {}; for (const stat of carConstants.defaultPartsStats[part]) { zeroStats[stat] = 0; } statsValues[part] = zeroStats; } } return statsValues; } export function updateTyreDegStats(designDictTeamReceiver, designDictTeamGiver, teamReceiver, teamGiver) { //only part 4 and 8 const reducedDesignDictTeamReceiver = {4: designDictTeamReceiver[4][0][0], 8: designDictTeamReceiver[8][0][0]}; for (const part in reducedDesignDictTeamReceiver){ let designID = reducedDesignDictTeamReceiver[part]; let newTyreDegStat = designDictTeamGiver[part][2]; let newTyreDegUnitValue = carConstants.valueToUnitValue[2](newTyreDegStat); queryDB(` UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = 2 `, [newTyreDegStat, newTyreDegUnitValue, designID], 'run'); queryDB(`UPDATE Parts_TeamExpertise SET Expertise = ? WHERE TeamID = ? AND PartType = ? AND PartStat = 2 `, [newTyreDegStat, teamReceiver, part], 'run'); } } export function applyExpertiseBoost(boost, team) { //multiply expertise for every stat for every part of the team by the boost queryDB(` UPDATE Parts_TeamExpertise SET Expertise = Expertise * ? WHERE TeamID = ? `, [boost, team], 'run'); } export function applyNextSeasonExpertiseBoost(boost, team) { queryDB(` UPDATE Parts_TeamExpertise SET NextSeasonExpertise = NextSeasonExpertise + ? WHERE TeamID = ? `, [boost, team], 'run'); } export function applyBoostToCarStats(designDict, boost, team) { const statsValues = {}; for (const part in designDict) { const designInfo = designDict[part][0]; const designID = (designInfo && designInfo.length) ? designInfo[0] : null; if (designID !== null) { const rows = queryDB(` SELECT PartStat, Value, UnitValue FROM Parts_Designs_StatValues WHERE DesignID = ? `, [designID], "allRows"); const tmp = {}; for (const [stat, val, unitVal] of rows) { if (stat !== 15) { let newUnitVal = applyScaledBoostToStatValue(unitVal, stat, boost); let newVal = carConstants.unitValueToValue[stat](newUnitVal); // console.log( // `Old UnitValue: ${unitVal}, New UnitValue: ${newUnitVal} | ` + // `Old Value: ${val}, New Value: ${newVal} | Part: ${part} | Stat: ${stat} | Team: ${team}` // ); queryDB( `UPDATE Parts_Designs_StatValues SET UnitValue = ?, Value = ? WHERE DesignID = ? AND PartStat = ?`, [newUnitVal, newVal, designID, stat], 'run' ); queryDB(` UPDATE Parts_TeamExpertise SET Expertise = ? WHERE TeamID = ? AND PartType = ? AND PartStat = ? `, [newVal, team, part, stat], 'run'); tmp[stat] = Math.round(newVal * 1000) / 1000; // redondeo a 3 decimales } } statsValues[part] = tmp; } else { const zeroStats = {}; for (const stat of carConstants.defaultPartsStats[part]) { zeroStats[stat] = 0; } statsValues[part] = zeroStats; } } return statsValues; } function applyScaledBoostToStatValue(originalValue, statID, boost) { // 1) Identificar el rango correspondiente a este stat const [minVal, maxVal] = carConstants.statsMinMax[statID] || [0, 100]; // fallback [0,100] si no está en el diccionario // Evitar división por cero en caso de minVal == maxVal const rangeSize = maxVal - minVal; if (rangeSize <= 0) { return originalValue; } // 2) Normalizar (0 a 1) let normalized = (originalValue - minVal) / rangeSize; // 3) Multiplicar por el boost normalized *= boost; // 4) Clamp a [0,1] if (normalized > 1) normalized = 1; if (normalized < 0) normalized = 0; // 5) Des-normalizar const newValue = minVal + normalized * rangeSize; return newValue; } /** * Devuelve el UnitValue de cada stat de un dict de diseños * (En Python: get_unitvalue_from_parts) */ export function getUnitValueFromParts(designDict) { const statsValues = {}; for (const part in designDict) { const designID = designDict[part][0][0]; const rows = queryDB(` SELECT PartStat, UnitValue FROM Parts_Designs_StatValues WHERE DesignID = ? `, [designID], 'allRows'); const tmp = {}; for (const [stat, unitVal] of rows) { tmp[stat] = unitVal; } statsValues[carConstants.parts[part]] = tmp; } return statsValues; } /** * UnitValue de un solo diseño * (get_unitvalue_from_one_part en Python) */ export function getTeamExpertise(teamId, yearIteration = null) { const expertise = {}; const partTypes = [3, 4, 5, 6, 7, 8]; partTypes.forEach((partType) => { expertise[carConstants.parts[partType]] = {}; }); const rows = queryDB(` SELECT PartType, PartStat, Expertise FROM Parts_TeamExpertise WHERE TeamID = ? AND PartType IN (3, 4, 5, 6, 7, 8) AND PartStat != 15 `, [teamId], 'allRows') || []; rows.forEach((row) => { const partType = Number(row[0]); const stat = Number(row[1]); const rawValue = Number(row[2]); const partKey = carConstants.parts[partType]; if (!partKey) return; let unitValue = rawValue; if (yearIteration === "24" && stat >= 7 && stat <= 9 && carConstants.downforce24ValueToUnitValue?.[stat]) { unitValue = carConstants.downforce24ValueToUnitValue[stat](rawValue); } else if (carConstants.valueToUnitValue?.[stat]) { unitValue = carConstants.valueToUnitValue[stat](rawValue); } expertise[partKey][stat] = Math.round(unitValue * 1000) / 1000; }); partTypes.forEach((partType) => { const partKey = carConstants.parts[partType]; const partStats = expertise[partKey]; for (const stat of carConstants.defaultPartsStats[partType] || []) { if (stat === 15) continue; if (partStats[stat] === undefined) { partStats[stat] = 0; } } }); return expertise; } export function updateTeamExpertise(teamId, expertiseUnitValues, yearIteration = null) { if (!expertiseUnitValues || typeof expertiseUnitValues !== "object") return; for (const partTypeKey of Object.keys(expertiseUnitValues)) { const partType = Number(partTypeKey); const stats = expertiseUnitValues[partTypeKey]; if (!Number.isFinite(partType) || !stats || typeof stats !== "object") continue; for (const statKey of Object.keys(stats)) { const stat = Number(statKey); const unitValue = Number(stats[statKey]); if (!Number.isFinite(stat) || !Number.isFinite(unitValue)) continue; if (stat === 15) continue; let value = unitValue; if (yearIteration === "24" && stat >= 7 && stat <= 9 && carConstants.downforce24UnitValueToValue?.[stat]) { value = carConstants.downforce24UnitValueToValue[stat](unitValue); } else if (carConstants.unitValueToValue?.[stat]) { value = carConstants.unitValueToValue[stat](unitValue); } queryDB(` UPDATE Parts_TeamExpertise SET Expertise = ? WHERE TeamID = ? AND PartType = ? AND PartStat = ? `, [value, teamId, partType, stat], 'run'); } } } export function getUnitValueFromOnePart(designId) { const partType = queryDB(` SELECT PartType FROM Parts_Designs WHERE DesignID = ? `, [designId], 'singleValue'); const rows = queryDB(` SELECT PartStat, UnitValue FROM Parts_Designs_StatValues WHERE DesignID = ? `, [designId], 'allRows'); const statsValues = {}; for (const [stat, uv] of rows) { statsValues[stat] = uv; } const partValues = {}; partValues[carConstants.parts[partType]] = statsValues; return partValues; } /** * Simple helper: convierte un porcentaje a valor físico según min/max * (convert_percentage_to_value en Python) */ export function convertPercentageToValue(attribute, percentage, minMax) { // minMax[attribute] = [min_value, max_value] const [minValue, maxValue] = minMax[attribute]; return minValue + (maxValue - minValue) * (percentage / 100.0); } /** * Pasa todos los atributos a rango human-readable * (make_attributes_readable en Python) */ export function makeAttributesReadable(attributes) { for (const attribute in attributes) { attributes[attribute] = convertPercentageToValue( attribute, attributes[attribute], carConstants.attributesMinMax ); // redondea a 3 dec attributes[attribute] = Math.round(attributes[attribute] * 1000) / 1000; attributes[attribute] = `${attributes[attribute]} ${carConstants.attributesUnits[attribute]}`; } return attributes; } /** * Calcula la performance global sumando (valorStat * contribución) * (calculate_overall_performance en Python) */ export function calculateOverallPerformance(attributes) { let ovr = 0; for (const attr in attributes) { ovr += attributes[attr] * carConstants.attributesContributions4[attr]; } return Math.round(ovr * 100) / 100; } /** * Devuelve un diccionario con las contribuciones * (get_contributors_dict en Python) */ export function getContributorsDict() { // Lógica similar a Python const contributorsValues = {}; const totalValues = {}; for (const attribute in carConstants.carAttributes) { totalValues[attribute] = 0; const referenceDict = carConstants[`${carConstants.carAttributes[attribute]}_contributors`]; // O donde sea que esté definido for (const stat in referenceDict) { totalValues[attribute] += referenceDict[stat]; } } for (const attribute in carConstants.carAttributes) { const referenceDict = carConstants[`${carConstants.carAttributes[attribute]}_contributors`]; contributorsValues[attribute] = {}; for (const stat in referenceDict) { contributorsValues[attribute][stat] = Math.round((referenceDict[stat] / totalValues[attribute]) * 1000) / 1000; } } return contributorsValues; } /** * Suma los factores de cada stat de cada parte * (get_part_stats_dict en Python) */ export function getPartStatsDict(carDict) { const partStats = {}; for (const part in carDict) { for (const stat in carDict[part]) { const factor = carConstants[`${carConstants.stats[stat]}_factors`][part]; if (!partStats[stat]) { partStats[stat] = 0; } partStats[stat] += carDict[part][stat] * factor; } } return partStats; } /** * Calcula los atributos finales sumando (contribución * partStats[stat]) / 10 * (calculate_car_attributes en Python) */ export function calculateCarAttributes(contributors, partsStats) { const attributesDict = {}; // Ajuste: partsStats[16] = (20000 - partsStats[15]) / 20 (como en el .py) partsStats[16] = (20000 - partsStats[15]) / 20; for (const attribute in contributors) { attributesDict[carConstants.carAttributes[attribute]] = 0; for (const stat in contributors[attribute]) { attributesDict[carConstants.carAttributes[attribute]] += (contributors[attribute][stat] * partsStats[stat]) / 10; } } return attributesDict; } /** * Obtiene días de carreras * (get_races_days en Python) */ export function getRacesDays() { const [day, season] = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow') || [0, 0]; // state=2 => completadas, state=0 => no comenzadas const races = queryDB(` SELECT RaceID, Day, TrackID FROM Races WHERE SeasonID = ? AND State = 2 `, [season], 'allRows'); // first_race_state_0 => la primera no iniciada const firstRaceState0 = queryDB(` SELECT RaceID, Day, TrackID FROM Races WHERE SeasonID = ? AND State = 0 ORDER BY Day ASC LIMIT 1 `, [season], 'singleRow'); if (firstRaceState0) { races.push(firstRaceState0); } return races; } export function getAllRaces() { const [day, season] = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow') || [0, 0]; const rows = queryDB(` SELECT RaceID, Day, TrackID FROM Races WHERE SeasonID = ? `, [season], 'allRows'); return rows; } function buildEnginePowerProgressionContext() { const seasonId = Number(queryDB(`SELECT CurrentSeason FROM Player_State`, [], 'singleValue')) || null; if (!seasonId) { return { enabled: false }; } const progressionTableExists = queryDB( `SELECT 1 FROM sqlite_master WHERE type='table' AND name='Custom_Engine_Progression'`, [], 'singleValue' ); if (!progressionTableExists) { return { enabled: false }; } const allocations = queryDB(`SELECT teamId, engineId FROM Custom_Engine_Allocations`, [], 'allRows') || []; const teamEngineIdByTeamId = {}; for (const row of allocations) { const teamId = Number(row?.[0]); const engineId = Number(row?.[1]); if (!teamId || !engineId) continue; teamEngineIdByTeamId[teamId] = engineId; } const currentPowerRows = queryDB(` SELECT engineId, unitValue FROM Custom_Engines_Stats WHERE designId = engineId AND partStat = 10 `, [], 'allRows') || []; const currentPowerByEngineId = {}; for (const row of currentPowerRows) { const engineId = Number(row?.[0]); const unitValue = Number(row?.[1]); if (!engineId || !Number.isFinite(unitValue)) continue; currentPowerByEngineId[engineId] = unitValue; } return { enabled: true, seasonId, teamEngineIdByTeamId, currentPowerByEngineId }; } function getEnginePowerUnitValueForRace(engineId, raceId, ctx) { const current = ctx?.currentPowerByEngineId?.[engineId]; if (!Number.isFinite(current)) { return null; } const snapshot = queryDB(` SELECT Power FROM Custom_Engine_Progression WHERE SeasonID = ? AND EngineID = ? AND RaceID > ? ORDER BY RaceID ASC LIMIT 1 `, [ctx.seasonId, engineId, raceId], 'singleValue'); if (snapshot !== null && snapshot !== undefined) { const snapNum = Number(snapshot); if (Number.isFinite(snapNum)) { return snapNum; } } return current; } /** * Devuelve la performance de todos los equipos en un día dado (o actual) * (get_performance_all_teams en Python) */ export function getPerformanceAllTeams(day = null, previous = null, customTeam = false, options = null) { const teams = {}; const contributors = getContributorsDict(); const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); const raceId = options?.raceId; const useHistoricalEnginePower = options?.useHistoricalEnginePower === true; const enginePowerCtx = options?.enginePowerCtx; const canOverrideEnginePower = Boolean( useHistoricalEnginePower && enginePowerCtx?.enabled && Number.isFinite(Number(raceId)) ); let enginePowerValueByEngineId = null; if (canOverrideEnginePower) { enginePowerValueByEngineId = {}; const enginesUsed = new Set(); for (const teamId of teamList) { const engineId = enginePowerCtx.teamEngineIdByTeamId?.[teamId]; if (engineId) enginesUsed.add(engineId); } const powerToValue = carConstants.engine_unitValueToValue?.[10]; if (typeof powerToValue === "function") { for (const engineId of enginesUsed) { const unitValue = getEnginePowerUnitValueForRace(engineId, Number(raceId), enginePowerCtx); if (!Number.isFinite(unitValue)) continue; const value = powerToValue(unitValue); enginePowerValueByEngineId[engineId] = Math.round(value * 1000) / 1000; } } } let parts; if (day == null) { // Usamos getBestParts parts = getBestParts(customTeam); } else { parts = getBestPartsUntil(day, customTeam); } for (const teamId of teamList) { const dict = getCarStats(parts[teamId]); if (enginePowerValueByEngineId && dict?.[0]) { const engineId = enginePowerCtx?.teamEngineIdByTeamId?.[teamId]; const overridePower = enginePowerValueByEngineId[engineId]; if (overridePower !== undefined && overridePower !== null) { dict[0][10] = overridePower; } } const partStats = getPartStatsDict(dict); const attributes = calculateCarAttributes(contributors, partStats); const ovr = calculateOverallPerformance(attributes); teams[teamId] = ovr; } return teams; } /** * Devuelve la performance de todos los coches (car1 y car2) de cada equipo * (get_performance_all_cars en Python) */ export function getPerformanceAllCars(customTeam = false) { const cars = {}; const contributors = getContributorsDict(); const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); // Este método en Python usaba "get_fitted_designs(custom_team=custom_team)" const carsParts = getFittedDesigns(customTeam); for (const teamId of Object.keys(carsParts)) { cars[teamId] = {}; for (const carId of Object.keys(carsParts[teamId])) { const dict = getCarStats(carsParts[teamId][carId]); // Falta ver si hay partes sin design const missingParts = []; for (const part in carsParts[teamId][carId]) { if (carsParts[teamId][carId][part][0][0] == null) { missingParts.push(part); } } const partStats = getPartStatsDict(dict); const attributes = calculateCarAttributes(contributors, partStats); const ovr = calculateOverallPerformance(attributes); const driverNumber = getDriverNumberWithCar(teamId, carId); cars[teamId][carId] = [ovr, driverNumber, missingParts]; } } return cars; } /** * Devuelve los atributos de todos los coches * (get_attributes_all_cars en Python) */ export function getAttributesAllCars(customTeam = false) { const cars = {}; const contributors = getContributorsDict(); const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); const carsParts = getFittedDesigns(customTeam); for (const teamId of Object.keys(carsParts)) { cars[teamId] = {}; for (const carId of Object.keys(carsParts[teamId])) { const dict = getCarStats(carsParts[teamId][carId]); const partStats = getPartStatsDict(dict); const attributes = calculateCarAttributes(contributors, partStats); // (En Python, se dejaba la opción de "make_attributes_readable") // attributes = makeAttributesReadable(attributes); cars[teamId][carId] = attributes; } } return cars; } /** * Devuelve el número del driver que conduce un coche concreto * (get_driver_number_with_car en Python) */ export function getDriverNumberWithCar(teamId, carId) { const row = queryDB(` SELECT con.StaffID FROM Staff_Contracts con JOIN Staff_GameData gam ON con.StaffID = gam.StaffID WHERE con.TeamID = ? AND gam.StaffType = 0 AND con.ContractType = 0 AND con.PosInTeam = ? `, [teamId, carId], 'singleRow'); if (!row) { return null; } const driverId = row[0]; const number = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder = ? `, [driverId], 'singleValue'); return number ?? null; } /** * Obtiene los diseños equipados en cada coche (loadout 1 y 2) de cada equipo * (get_fitted_designs en Python) */ export function getFittedDesigns(customTeam = false) { const teams = {}; const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); for (const t of teamList) { teams[t] = {}; // loadout => 1 o 2 for (let loadout = 1; loadout <= 2; loadout++) { const designs = {}; for (let part = 3; part < 9; part++) { const row = queryDB(` SELECT DesignID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = ? `, [t, part, loadout], 'allRows'); designs[part] = row; } // engine const engine = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = 0 AND TeamID = ? `, [t], 'allRows'); designs[0] = engine; teams[t][loadout] = designs; } } return teams; } // Asumiendo que tu clase CarAnalysisUtils ya tiene otros métodos traducidos // Añadimos/completamos con estos métodos: export function fitLatestDesignsAllGrid(customTeam = false) { // SELECT Day, CurrentSeason FROM Player_State const row = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], "singleRow"); if (!row) { console.warn("No Player_State data found."); return; } const [day, season] = row; // Obtenemos las mejores piezas hasta 'day' const bestParts = getBestPartsUntil(day, customTeam); // Para cada equipo en bestParts for (const team of Object.keys(bestParts)) { fitLatestDesignsOneTeam(team, bestParts[team]); } // conn.commit() (en SQL.js no es necesario típicamente) } export function fitLatestDesignsOneTeam(teamId, parts) { // Recorremos loadout = 1 y 2 for (let loadout = 1; loadout <= 2; loadout++) { // Para cada 'part' en el objeto parts for (const partKey of Object.keys(parts)) { const part = Number(partKey); if (part !== 0) { // En Python, parts[part] = [[designId], ...], asumiendo la estructura const design = parts[part][0][0]; // -> designID // fitted_design actual const fittedRow = queryDB(` SELECT DesignID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = ? `, [teamId, part, loadout], "singleRow"); if (!fittedRow) { console.warn(`No fittedRow found for TeamID=${teamId}, part=${part}, loadout=${loadout}`); continue; } const fittedDesign = fittedRow[0]; if (design !== fittedDesign) { // Buscamos items disponibles const partsAvailable = queryDB(` SELECT ItemID FROM Parts_Items WHERE DesignID = ? AND AssociatedCar IS NULL `, [design], "allRows"); if (!partsAvailable.length) { // no hay items disponibles => creamos uno nuevo const item = createNewItem(design, part); addPartToLoadout(design, part, teamId, loadout, item); } else { const item = partsAvailable[0][0]; // primer item addPartToLoadout(design, part, teamId, loadout, item); } } else { // design ya está equipado en este loadout // Miramos si loadout 1 y 2 comparten item const otherLoadout = (loadout === 2) ? 1 : 2; const fittedItemOther = queryDB(` SELECT ItemID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = ? `, [teamId, part, otherLoadout], "singleRow"); const fittedItem = queryDB(` SELECT ItemID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = ? `, [teamId, part, loadout], "singleRow"); if (fittedItemOther && fittedItem && fittedItemOther[0] === fittedItem[0]) { // Ambos loadouts tienen el mismo item => creamos uno nuevo const item = createNewItem(design, part); addPartToLoadout(design, part, teamId, loadout, item); } } } } } // commit // (en SQL.js no es necesario, pero podrías hacer db.run("BEGIN/COMMIT") si fuera el caso) } export function updateItemsForDesignDict(designDict, teamId) { for (const designKey of Object.keys(designDict)) { const design = Number(designKey); const nParts = parseInt(designDict[designKey], 10); // SELECT PartType FROM Parts_Designs WHERE DesignID = {design} const partType = queryDB(` SELECT PartType FROM Parts_Designs WHERE DesignID = ? `, [design], "singleValue"); // SELECT COUNT(*) FROM Parts_Items WHERE DesignID = {design} AND BuildWork = X let actualParts = queryDB(` SELECT COUNT(*) FROM Parts_Items WHERE DesignID = ? AND BuildWork = ? `, [design, carConstants.standardBuildworkPerPart[partType]], "singleValue"); if (actualParts == null) actualParts = 0; let diff = nParts - actualParts; if (diff > 0) { while (diff > 0) { createNewItem(design, partType); diff--; } } else if (diff < 0) { while (diff < 0) { deleteItem(design); diff++; } } } // commit } export function fitLoadoutsDict(loadoutsDict, teamId) { for (const partKey of Object.keys(loadoutsDict)) { const part = Number(partKey); const design1 = loadoutsDict[part][0]; const design2 = loadoutsDict[part][1]; // SELECT DesignID, ItemID FROM Parts_CarLoadout ... let fittedDesign1 = queryDB(` SELECT DesignID, ItemID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = 1 `, [teamId, part], "singleRow"); if (design1 != null) { if (fittedDesign1 && fittedDesign1[0] != null && fittedDesign1[1] != null) { // "UPDATE Parts_Items SET AssociatedCar = NULL WHERE ItemID = ?" const itemId = fittedDesign1[1]; queryDB(` UPDATE Parts_Items SET AssociatedCar = NULL WHERE ItemID = ? `, [itemId], 'run'); // fittedDesign1 = fittedDesign1[0] fittedDesign1 = [fittedDesign1[0], itemId]; // si necesitas retenerlo } // Si la design1 actual es distinta... if (!fittedDesign1 || fittedDesign1[0] !== design1) { // SELECT ItemID FROM Parts_Items WHERE ... const items1 = queryDB(` SELECT ItemID FROM Parts_Items WHERE DesignID = ? AND BuildWork = ? AND AssociatedCar IS NULL `, [design1, carConstants.standardBuildworkPerPart[part]], "allRows"); let item1; if (!items1.length) { item1 = createNewItem(design1, part); } else { item1 = items1[0][0]; } addPartToLoadout(design1, part, teamId, 1, item1); } } // Ahora loadout 2 let fittedDesign2 = queryDB(` SELECT DesignID, ItemID FROM Parts_CarLoadout WHERE TeamID = ? AND PartType = ? AND LoadoutID = 2 `, [teamId, part], "singleRow"); if (design2 != null) { if (fittedDesign2 && fittedDesign2[0] != null && fittedDesign2[1] != null) { const itemId2 = fittedDesign2[1]; queryDB(` UPDATE Parts_Items SET AssociatedCar = NULL WHERE ItemID = ? `, [itemId2], 'run'); fittedDesign2 = [fittedDesign2[0], itemId2]; } if (!fittedDesign2 || fittedDesign2[0] !== design2) { const items2 = queryDB(` SELECT ItemID FROM Parts_Items WHERE DesignID = ? AND BuildWork = ? AND AssociatedCar IS NULL `, [design2, carConstants.standardBuildworkPerPart[part]], "allRows"); let item2; if (!items2.length) { item2 = createNewItem(design2, part); } else { item2 = items2[0][0]; } addPartToLoadout(design2, part, teamId, 2, item2); } } } // commit } // En Python: create_new_item(design_id, part) export function createNewItem(designId, part) { // SELECT MAX(ItemID) FROM Parts_Items let maxItem = queryDB(` SELECT MAX(ItemID) FROM Parts_Items `, [], "singleValue"); const newItem = maxItem + 1; const numberOfManufactures = queryDB(` SELECT ManufactureCount FROM Parts_Designs WHERE DesignID = ? `, [designId], "singleValue"); const newNManufactures = numberOfManufactures + 1; queryDB(` INSERT INTO Parts_Items VALUES ( ?, ?, ?, 1, ?, NULL, NULL, 0, NULL ) `, [newItem, designId, carConstants.standardBuildworkPerPart[part], newNManufactures], 'run'); queryDB(` UPDATE Parts_Designs SET ManufactureCount = ? WHERE DesignID = ? `, [newNManufactures, designId], 'run'); return newItem; } export function deleteItem(designId) { // SELECT PartType FROM Parts_Designs WHERE DesignID = {designId} const partType = queryDB(` SELECT PartType FROM Parts_Designs WHERE DesignID = ? `, [designId], "singleValue"); // SELECT ItemID FROM Parts_Items WHERE DesignID = {designId} AND BuildWork = ... const item = queryDB(` SELECT ItemID FROM Parts_Items WHERE DesignID = ? AND BuildWork = ? `, [designId, carConstants.standardBuildworkPerPart[partType]], "singleValue"); queryDB(` DELETE FROM Parts_Items WHERE ItemID = ? `, [item], 'run'); } export function addNewDesign(part, teamId, day, season, latestDesignPartFromTeam, newDesignId) { const maxDesignFromPart = queryDB(` SELECT MAX(DesignNumber) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? `, [part, teamId], "singleValue"); const newMaxDesign = maxDesignFromPart + 1; queryDB(` UPDATE Parts_Designs_TeamData SET NewDesignsThisSeason = ? WHERE TeamID = ? AND PartType = ? `, [newMaxDesign, teamId, part], 'run'); //check if newDesignId already exists (it shouldn't, but just in case) const existingDesign = queryDB(` SELECT DesignID FROM Parts_Designs WHERE DesignID = ? `, [newDesignId], 'singleValue') if (existingDesign) { return; } queryDB(` INSERT INTO Parts_Designs VALUES ( ?, ?, 6720, 6600, ?, ?, NULL, 5, 1, 0, 0, 1500, ?, 0, 0, 4, ?, 1, ?, 1 ) `, [newDesignId, part, day - 1, day, season, newMaxDesign, teamId], 'run'); queryDB(` INSERT INTO Parts_DesignHistoryData VALUES ( ?, 0, 0, 0, 0 ) `, [newDesignId], 'run'); copyFromTable("building", latestDesignPartFromTeam, newDesignId); copyFromTable("staff", latestDesignPartFromTeam, newDesignId); add4Items(newDesignId, part, teamId); } export function copyFromTable(table, latestDesignId, newDesignId) { let tableName = ""; if (table === "building") { tableName = "Parts_Designs_BuildingEffects"; } else if (table === "staff") { tableName = "Parts_Designs_StaffEffects"; } const rows = queryDB(` SELECT * FROM ${tableName} WHERE DesignID = ? `, [latestDesignId], "allRows"); for (const row of rows) { // row => [DesignID, col1, col2, ...] queryDB(` INSERT INTO ${tableName} VALUES (?, ?, ?, 0) `, [newDesignId, row[1], row[2]], 'run'); } } export function add4Items(newDesignId, part, teamId) { let maxItem = queryDB(` SELECT MAX(ItemID) FROM Parts_Items `, [], "singleValue"); for (let i = 1; i <= 4; i++) { maxItem += 1; queryDB(` INSERT INTO Parts_Items VALUES ( ?, ?, ?, 1, ?, NULL, NULL, 0, NULL ) `, [maxItem, newDesignId, carConstants.standardBuildworkPerPart[part], i], 'run'); // Para loadout 1 y 2 if (i <= 2) { const loadoutId = i; addPartToLoadout(newDesignId, part, teamId, loadoutId, maxItem); } } } export function addPartToLoadout(designId, part, teamId, loadoutId, itemId) { queryDB(` UPDATE Parts_CarLoadout SET DesignID = ?, ItemID = ? WHERE TeamID = ? AND PartType = ? AND LoadoutID = ? `, [designId, itemId, teamId, part, loadoutId], 'run'); queryDB(` UPDATE Parts_Items SET AssociatedCar = ?, LastEquippedCar = ? WHERE ItemID = ? `, [loadoutId, loadoutId, itemId], 'run'); } export function overwritePerformanceTeam(teamId, performance, customTeam = null, yearIteration = null, loadoutDict = null) { const row = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!row) { console.warn("Player_State not found"); return; } const [day, season] = row; const bestParts = getBestPartsUntil(day, customTeam); const teamParts = bestParts[Number(teamId)]; for (const partKey of Object.keys(teamParts)) { const part = Number(partKey); if (part !== 0) { const design = teamParts[part][0][0]; // design actual const partName = carConstants.parts[part]; // "Suspension", "Wing", etc. const newDesign = performance[partName]["designEditing"]; delete performance[partName]["designEditing"]; let latestDesignPartFromTeam = null; let finalDesign = design; if (Number(newDesign) === -1) { // new part const maxDesign = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs `, [], 'singleValue'); latestDesignPartFromTeam = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? `, [part, teamId], 'singleValue'); const newDesignId = maxDesign + 1; addNewDesign(part, Number(teamId), day, season, latestDesignPartFromTeam, newDesignId); finalDesign = newDesignId; } else { finalDesign = Number(newDesign); } const statsObj = performance[partName]; for (const statKey of Object.keys(statsObj)) { const statNum = parseFloat(statsObj[statKey]); let value; if (yearIteration === "24" && Number(statKey) >= 7 && Number(statKey) <= 9) { value = carConstants.downforce24UnitValueToValue[statKey](statNum); } else { value = carConstants.unitValueToValue[statKey](statNum); } if (Number(newDesign) !== -1) { // update changeExpertiseBased(part, statKey, value, Number(teamId)); queryDB(` UPDATE Parts_Designs_StatValues SET UnitValue = ? WHERE DesignID = ? AND PartStat = ? `, [statsObj[statKey], finalDesign, statKey], 'run'); queryDB(` UPDATE Parts_Designs_StatValues SET Value = ? WHERE DesignID = ? AND PartStat = ? `, [value, finalDesign, statKey], 'run'); } else { // insert queryDB(` INSERT INTO Parts_Designs_StatValues VALUES ( ?, ?, ?, ?, 0.5, 1, 0.1 ) `, [finalDesign, statKey, value, statsObj[statKey]], 'run'); } } // si newDesign == -1 => insertamos el peso standard if (Number(newDesign) === -1) { queryDB(` INSERT INTO Parts_Designs_StatValues VALUES ( ?, 15, 500, ?, 0.5, 0, 0 ) `, [finalDesign, carConstants.standardWeightPerPart[part]], 'run'); // Tras insertar stats, cambiamos expertise for (const statKey of Object.keys(statsObj)) { const statNum = parseFloat(statsObj[statKey]); let value; if (yearIteration === "24" && Number(statKey) >= 7 && Number(statKey) <= 9) { value = carConstants.downforce24UnitValueToValue[statKey](statNum); } else { value = carConstants.unitValueToValue[statKey](statNum); } changeExpertiseBased(part, statKey, value, Number(teamId), "new", latestDesignPartFromTeam); } } } } // commit } export function changeExpertiseBased(part, stat, newValue, teamId, type = "existing", oldDesign = null) { // SELECT Day, CurrentSeason FROM Player_State const row = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!row) { console.warn("No Player_State found to do expertise changes"); return; } const [day, curSeason] = row; let currentValue = null; if (type === "existing") { // SELECT MAX(Value) FROM Parts_Designs_StatValues ... currentValue = queryDB(` SELECT MAX(Value) FROM Parts_Designs_StatValues WHERE PartStat = ? AND DesignID IN ( SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? AND ValidFrom = ? ) `, [stat, part, teamId, curSeason], 'singleValue'); } else if (type === "new") { // SELECT Value FROM Parts_Designs_StatValues ... currentValue = queryDB(` SELECT Value FROM Parts_Designs_StatValues WHERE PartStat = ? AND DesignID = ? `, [stat, oldDesign], 'singleValue'); } if (!currentValue) { currentValue = 1; // si no hay valor } if (currentValue === 0) { currentValue = 1; } const currentExpertise = queryDB(` SELECT Expertise FROM Parts_TeamExpertise WHERE TeamID = ? AND PartType = ? AND PartStat = ? `, [teamId, part, stat], 'singleValue') || 0; // console.log(newValue, currentValue, currentExpertise); const newExpertise = (Number(newValue) * Number(currentExpertise)) / Number(currentValue); // console.log(`Old expertise: ${currentExpertise}, New expertise: ${newExpertise}`); queryDB(` UPDATE Parts_TeamExpertise SET Expertise = ? WHERE TeamID = ? AND PartType = ? AND PartStat = ? `, [newExpertise, teamId, part, stat], 'run'); } export function getPerformanceAllTeamsSeason(customTeam = false, options = {}) { const useHistoricalEnginePower = options?.useHistoricalEnginePower === true; const enginePowerCtx = useHistoricalEnginePower ? buildEnginePowerProgressionContext() : null; const races = getRacesDays(); const firstDay = getFirstDaySeason(); // Insertamos al principio (0, firstDay, 0) races.unshift([0, firstDay, 0]); // similar a insert(0, first_tuple) const racesPerformances = []; let previous = null; for (const raceDay of races) { // raceDay => [RaceID, Day, TrackID], en python pilla el day en [1] const raceId = raceDay[0]; const day = raceDay[1]; const performances = getPerformanceAllTeams(day, previous, customTeam, { raceId, useHistoricalEnginePower, enginePowerCtx }); racesPerformances.push(performances); previous = performances; } const allRaces = getAllRaces(); return [racesPerformances, allRaces]; } export function getAduoEngineUpgradeRaceIds(seasonId = null) { const resolvedSeasonId = Number(seasonId) || Number(queryDB(`SELECT CurrentSeason FROM Player_State`, [], 'singleValue')) || null; if (!resolvedSeasonId) return []; const progressionTableExists = queryDB( `SELECT 1 FROM sqlite_master WHERE type='table' AND name='Custom_Engine_Progression'`, [], 'singleValue' ); if (!progressionTableExists) return []; const rows = queryDB(` SELECT DISTINCT RaceID FROM Custom_Engine_Progression WHERE SeasonID = ? AND Source IN ('pre_aduo_tp', 'pre_engine_edit') ORDER BY RaceID ASC `, [resolvedSeasonId], 'allRows') || []; return rows .map(r => Number(r?.[0])) .filter(raceId => Number.isFinite(raceId) && raceId > 0); } export function getFirstDaySeason() { const query = ` SELECT Number, COUNT(*) as Occurrences FROM ( SELECT DayCreated as Number FROM Parts_Designs UNION ALL SELECT DayCompleted as Number FROM Parts_Designs ) Combined GROUP BY Number ORDER BY Occurrences DESC LIMIT 1; `; const row = queryDB(query, [], 'singleRow'); if (!row) { console.warn("No firstDay found"); return 0; } const firstDay = row[0]; return firstDay; } export function getAttributesAllTeams(customTeam = false) { const teams = {}; const contributors = getContributorsDict(); const bestParts = getBestParts(customTeam); const teamList = customTeam ? [...Array(10).keys()].map(i => i + 1).concat(32) : [...Array(10).keys()].map(i => i + 1); for (const i of teamList) { const dict = getCarStats(bestParts[i]); const partStats = getPartStatsDict(dict); const attributes = calculateCarAttributes(contributors, partStats); attributes.engine_power = getOneStatUnitValueFromTeam(0, 10, i) || 0; teams[i] = attributes; } return teams; } export function getOneStatUnitValueFromTeam(part, stat, teamId) { const designId = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs WHERE PartType = ? AND TeamID = ? `, [part, teamId], 'singleValue'); if (designId){ const unitValue = queryDB(` SELECT UnitValue FROM Parts_Designs_StatValues WHERE DesignID = ? AND PartStat = ? `, [designId, stat], 'singleValue'); return unitValue; } } export function getMaxDesign() { const val = queryDB(` SELECT MAX(DesignID) FROM Parts_Designs `, [], 'singleValue'); return val; } export function deleteCustomEngineAndReassign(engineIdRaw, fallbackEngineIdRaw) { const engineId = Number(engineIdRaw); if (!engineId || engineId <= 10) { return { ok: false, error: "Invalid custom engine id" }; } let fallbackEngineId = Number(fallbackEngineIdRaw); if (!fallbackEngineId || fallbackEngineId === engineId) { fallbackEngineId = Number(queryDB( `SELECT engineID FROM Custom_Engines_List WHERE engineID <= 10 ORDER BY engineID ASC LIMIT 1`, [], "singleValue" )); } if (!fallbackEngineId || fallbackEngineId === engineId) { fallbackEngineId = Number(queryDB( `SELECT engineID FROM Custom_Engines_List WHERE engineID != ? ORDER BY engineID ASC LIMIT 1`, [engineId], "singleValue" )); } if (!fallbackEngineId || fallbackEngineId === engineId) { return { ok: false, error: "No fallback engine available" }; } const teamsSupplied = queryDB( `SELECT teamId FROM Custom_Engine_Allocations WHERE engineId = ?`, [engineId], "allRows" ) || []; teamsSupplied.forEach(team => { const teamId = Number(team?.[0]); if (!teamId) return; manage_engine_change(teamId, fallbackEngineId); }); queryDB(`DELETE FROM Custom_Engine_Allocations WHERE engineId = ?`, [engineId], "run"); queryDB(`DELETE FROM Custom_Engines_Stats WHERE engineId = ?`, [engineId], "run"); queryDB(`DELETE FROM Custom_Engines_List WHERE engineId = ?`, [engineId], "run"); return { ok: true, fallbackEngineId, reassignedTeams: teamsSupplied.length }; } ================================================ FILE: src/js/backend/scriptUtils/carConstants.js ================================================ export const stats = { 0: "airflow_front", 1: "airflow_sensitivity", 2: "brake_cooling", 3: "drs_delta", 4: "drag_reduction", 5: "engine_cooling", 6: "fuel_efficiency", 7: "low_speed_downforce", 8: "medium_speed_downforce", 9: "high_speed_downforce", 10: "power", 11: "performance_loss", 12: "performance_threshold", 13: "airflow_middle", 14: "operational_range", 15: "lifespan", 16: "special_weight" }; export const defaultPartsStats = { 3: [3, 4, 5, 13, 15], 4: [0, 1, 2, 7, 8, 9, 15], 5: [1, 3, 4, 7, 8, 9, 15], 6: [0, 4, 5, 13, 15], 7: [1, 4, 7, 8, 9, 15], 8: [0, 2, 4, 7, 8, 9, 15] }; export const unitValueToValue = { 0: (x) => x * 10, 1: (x) => x * 10, 2: (x) => x * 10, 3: (x) => x * 10, 4: (x) => x * 10, 5: (x) => x * 10, 6: (x) => (x - 90) * 1000 / 10, 7: (x) => (x - 3) / 0.002, 8: (x) => (x - 5) / 0.002, 9: (x) => (x - 7) / 0.001, 10: (x) => (x - 90) * 1000 / 10, 11: (x) => (85 - x) * 1000 / 20, 12: (x) => (x - 70) * 1000 / 15, 13: (x) => x * 10, 14: (x) => (85 - x) * 1000 / 15, 15: (x) => (x - 40) * 1000 / 30, 18: (x) => (x - 40) * 1000 / 30, 19: (x) => (x - 40) * 1000 / 30 }; export const valueToUnitValue = { 0: (x) => x / 10, 1: (x) => x / 10, 2: (x) => x / 10, 3: (x) => x / 10, 4: (x) => x / 10, 5: (x) => x / 10, 6: (x) => x / 100 + 90, 7: (x) => x * 0.002 + 3, 8: (x) => x * 0.002 + 5, 9: (x) => x * 0.001 + 7, 10: (x) => x / 100 + 90, 11: (x) => 85 - x / 50, 12: (x) => x * 15 / 1000 + 70, 13: (x) => x / 10, 14: (x) => 85 - x * 15 / 1000, 15: (x) => x * 30 / 1000 + 40, 18: (x) => x * 30 / 1000 + 40, 19: (x) => x * 30 / 1000 + 40 }; export const engine_unitValueToValue = { 6: (x) => 20 * (x - 50), 10: (x) => 50 * (x - 80), 11: (x) => -50 * (x - 85), 12: (x) => (200 / 3) * (x - 70), 14: (x) => 50 * (x - 60), 18: (x) => 50 * (x - 50), 19: (x) => 50 * (x - 50), } export const downforce24UnitValueToValue = { 7: (x) => 497.6 * x - 1489.8, 8: (x) => 496.8 * x - 2479.5, 9: (x) => 974.048 * x - 6803.2614 }; export const downforce24ValueToUnitValue = { 7: (x) => (x + 1489.8) / 497.6, 8: (x) => (x + 2479.5) / 496.8, 9: (x) => (x + 6803.2614) / 974.048 }; export const parts = { 0: "engine", 3: "chassis", 4: "front_wing", 5: "rear_wing", 6: "sidepods", 7: "underfloor", 8: "suspension" }; export const standardWeightPerPart = { 3: 5150, 4: 2625, 5: 3125, 6: 4125, 7: 3550, 8: 2900 }; export const standardBuildworkPerPart = { 3: 2000, 4: 500, 5: 500, 6: 1500, 7: 1500, 8: 1500 }; export const optimalWeightPerPart = { 3: 4070, 4: 1525, 5: 1945, 6: 3025, 7: 2390, 8: 1940 }; export const minimalWeightPerPart = { 3: 3800, 4: 1250, 5: 1650, 6: 2750, 7: 2100, 8: 1700 }; export const carAttributes = { 0: "top_speed", 1: "acceleration", 2: "drs", 3: "low_speed", 4: "medium_speed", 5: "high_speed", 6: "dirty_air", 7: "brake_cooling", 8: "engine_cooling" }; export const statsMinMax = { 0: [0, 100], 1: [0, 100], 2: [0, 100], 3: [0, 100], 4: [0, 100], 5: [0, 100], 7: [3, 5], 8: [5, 7], 9: [7, 8], 10: [90, 100], 13: [0, 100] }; export const lifespanPartsMinMax = { 3: [3800, 6500], 4: [1250, 4000], 5: [1650, 4600], 6: [2750, 5500], 7: [2100, 5000], 8: [1700, 4100] }; export const attributesMinMax = { top_speed: [313.00, 328.00], acceleration: [1.800, 1.900], drs: [0, 100], low_speed: [2.000, 3.000], medium_speed: [3.000, 4.000], high_speed: [4.000, 5.500], dirty_air: [0, 100], brake_cooling: [0, 100], engine_cooling: [0, 100] }; export const attributesUnits = { top_speed: "km/h", acceleration: "G", drs: "%", low_speed: "G", medium_speed: "G", high_speed: "G", dirty_air: "%", brake_cooling: "%", engine_cooling: "%" }; export const attributesContributions = { top_speed: 0.15, acceleration: 0, drs: 0.15, low_speed: 0.1666, medium_speed: 0.1666, high_speed: 0.1666, dirty_air: 0.0666, brake_cooling: 0.0666, engine_cooling: 0.0666 }; export const attributesContributions2 = { top_speed: 0.15, acceleration: 0, drs: 0.15, low_speed: 0.2166, medium_speed: 0.2166, high_speed: 0.2166, dirty_air: 0.03, brake_cooling: 0.01, engine_cooling: 0.01 }; export const attributesContributions3 = { top_speed: 0.144, acceleration: 0.018, drs: 0.115, low_speed: 0.195, medium_speed: 0.195, high_speed: 0.195, dirty_air: 0.029, brake_cooling: 0.078, engine_cooling: 0.031 }; /* way more impact to accelaration*/ export const attributesContributions4 = { top_speed: 0.15, acceleration: 0.05, drs: 0.1, low_speed: 0.18, medium_speed: 0.18, high_speed: 0.18, dirty_air: 0.05, brake_cooling: 0.05, engine_cooling: 0.015 } export const fuel_efficiency_factors = { 0: 1 }; export const power_factors = { 0: 1 }; export const performance_loss_factors = { 0: 1 }; export const performance_threshold_factors = { 0: 1 }; export const operational_range_factors = { 0: 1 }; export const lifespan_factors = { 1: 0, 2: 0, 3: 5, 4: 2, 5: 3, 6: 5, 7: 4, 8: 1 }; export const drag_reduction_factors = { 3: 0.2, 5: 0.3, 6: 0.2, 7: 0.2, 8: 0.1 }; export const engine_cooling_factors = { 3: 0.4, 6: 0.6 }; export const airflow_middle_factors = { 3: 0.6, 6: 0.4 }; export const airflow_front_factors = { 4: 0.4, 6: 0.2, 8: 0.4 }; export const airflow_sensitivity_factors = { 4: 0.4, 5: 0.4, 7: 0.2 }; export const brake_cooling_factors = { 4: 0.4, 8: 0.6 }; export const low_speed_downforce_factors = { 4: 0.2, 5: 0.2, 7: 0.3, 8: 0.3 }; export const medium_speed_downforce_factors = { 4: 0.2, 5: 0.2, 7: 0.5, 8: 0.1 }; export const high_speed_downforce_factors = { 4: 0.2, 5: 0.2, 7: 0.5, 8: 0.1 }; export const drs_delta_factors = { 5: 0.75, 3: 0.25 }; export const top_speed_contributors = { 4: 1 }; export const acceleration_contributors = { 10: 0.5, 4: 0.5, 16: 0.15 }; export const drs_contributors = { 3: 1 }; export const low_speed_contributors = { 0: 0.6, 7: 1, 16: 0.24 }; export const medium_speed_contributors = { 0: 0.4, 13: 0.4, 8: 1, 16: 0.27 }; export const high_speed_contributors = { 13: 0.6, 9: 1, 16: 0.24 }; export const dirty_air_contributors = { 1: 1 }; export const brake_cooling_contributors = { 2: 1 }; export const engine_cooling_contributors = { 5: 1 }; ================================================ FILE: src/js/backend/scriptUtils/countries.js ================================================ export const countries_abreviations = { "Andorra": "AD", "United Arab Emirates": "AE", "Afghanistan": "AF", "Antigua and Barbuda": "AG", "Anguilla": "AI", "Albania": "AL", "Armenia": "AM", "Netherlands Antilles": "AN", "Angola": "AO", "Antarctica": "AQ", "Argentina": "AR", "American Samoa": "AS", "Austria": "AT", "Australia": "AU", "Aruba": "AW", "Åland Islands": "AX", "Azerbaijan": "AZ", "Bosnia and Herzegovina": "BA", "Barbados": "BB", "Bangladesh": "BD", "Belgium": "BE", "Burkina Faso": "BF", "Bulgaria": "BG", "Bahrain": "BH", "Burundi": "BI", "Benin": "BJ", "Saint Barthélemy": "BL", "Bermuda": "BM", "Brunei Darussalam": "BN", "Bolivia": "BO", "Brazil": "BR", "Bahamas": "BS", "Bhutan": "BT", "Bouvet Island": "BV", "Botswana": "BW", "Belarus": "BY", "Belize": "BZ", "Canada": "CA", "Cocos (Keeling) Islands": "CC", "Congo, Democratic Republic of the": "CD", "Central African Republic": "CF", "Congo, Republic of the": "CG", "Switzerland": "CH", "Côte d'Ivoire": "CI", "Cook Islands": "CK", "Chile": "CL", "Cameroon": "CM", "China": "CN", "Colombia": "CO", "Costa Rica": "CR", "Cuba": "CU", "Cape Verde": "CV", "Curaçao": "CW", "Christmas Island": "CX", "Cyprus": "CY", "Czech Republic": "CZ", "Germany": "DE", "Djibouti": "DJ", "Denmark": "DK", "Dominica": "DM", "Dominican Republic": "DO", "Algeria": "DZ", "Ecuador": "EC", "Estonia": "EE", "Egypt": "EG", "Western Sahara": "EH", "Eritrea": "ER", "Spain": "ES", "Ethiopia": "ET", "Finland": "FI", "Fiji": "FJ", "Falkland Islands (Malvinas)": "FK", "Micronesia, Federated States of": "FM", "Faroe Islands": "FO", "France": "FR", "Gabon": "GA", "United Kingdom": "GB", "Grenada": "GD", "Georgia": "GE", "French Guiana": "GF", "Guernsey": "GG", "Ghana": "GH", "Gibraltar": "GI", "Greenland": "GL", "Gambia": "GM", "Guinea": "GN", "Guadeloupe": "GP", "Equatorial Guinea": "GQ", "Greece": "GR", "South Georgia and the South Sandwich Islands": "GS", "Guatemala": "GT", "Guam": "GU", "Guinea-Bissau": "GW", "Guyana": "GY", "Hong Kong": "HK", "Heard Island and McDonald Islands": "HM", "Honduras": "HN", "Croatia": "HR", "Haiti": "HT", "Hungary": "HU", "Indonesia": "ID", "Ireland": "IE", "Israel": "IL", "Isle of Man": "IM", "India": "IN", "British Indian Ocean Territory": "IO", "Iraq": "IQ", "Iran, Islamic Republic of": "IR", "Iceland": "IS", "Italy": "IT", "Jersey": "JE", "Jamaica": "JM", "Jordan": "JO", "Japan": "JP", "Kenya": "KE", "Kyrgyzstan": "KG", "Cambodia": "KH", "Kiribati": "KI", "Comoros": "KM", "Saint Kitts and Nevis": "KN", "Korea, Democratic People's Republic of": "KP", "Korea, Republic of": "KR", "Kuwait": "KW", "Cayman Islands": "KY", "Kazakhstan": "KZ", "Lao People's Democratic Republic": "LA", "Lebanon": "LB", "Saint Lucia": "LC", "Liechtenstein": "LI", "Sri Lanka": "LK", "Liberia": "LR", "Lesotho": "LS", "Lithuania": "LT", "Luxembourg": "LU", "Latvia": "LV", "Libya": "LY", "Morocco": "MA", "Monaco": "MC", "Moldova, Republic of": "MD", "Montenegro": "ME", "Saint Martin (French part)": "MF", "Madagascar": "MG", "Marshall Islands": "MH", "Macedonia, the Former Yugoslav Republic of": "MK", "Mali": "ML", "Myanmar": "MM", "Mongolia": "MN", "Macao": "MO", "Northern Mariana Islands": "MP", "Martinique": "MQ", "Mauritania": "MR", "Montserrat": "MS", "Malta": "MT", "Mauritius": "MU", "Maldives": "MV", "Malawi": "MW", "Mexico": "MX", "Malaysia": "MY", "Mozambique": "MZ", "Namibia": "NA", "New Caledonia": "NC", "Niger": "NE", "Norfolk Island": "NF", "Nigeria": "NG", "Nicaragua": "NI", "Netherlands": "NL", "Norway": "NO", "Nepal": "NP", "Nauru": "NR", "Niue": "NU", "New Zealand": "NZ", "Oman": "OM", "Panama": "PA", "Peru": "PE", "French Polynesia": "PF", "Papua New Guinea": "PG", "Philippines": "PH", "Pakistan": "PK", "Poland": "PL", "Saint Pierre and Miquelon": "PM", "Pitcairn": "PN", "Puerto Rico": "PR", "Palestine, State of": "PS", "Portugal": "PT", "Palau": "PW", "Paraguay": "PY", "Qatar": "QA", "Réunion": "RE", "Romania": "RO", "Serbia": "RS", "Russian": "RU", "Rwanda": "RW", "Saudi Arabia": "SA", "Solomon Islands": "SB", "Seychelles": "SC", "Sudan": "SD", "Sweden": "SE", "Singapore": "SG", "Saint Helena, Ascension and Tristan da Cunha": "SH", "Slovenia": "SI", "Svalbard and Jan Mayen": "SJ", "Slovakia": "SK", "Sierra Leone": "SL", "San Marino": "SM", "Senegal": "SN", "Somalia": "SO", "Suriname": "SR", "South Sudan": "SS", "Sao Tome and Principe": "ST", "El Salvador": "SV", "Sint Maarten (Dutch part)": "SX", "Syrian Arab Republic": "SY", "Swaziland": "SZ", "Turks and Caicos Islands": "TC", "Chad": "TD", "French Southern Territories": "TF", "Togo": "TG", "Thailand": "TH", "Tajikistan": "TJ", "Tokelau": "TK", "Timor-Leste": "TL", "Turkmenistan": "TM", "Tunisia": "TN", "Tonga": "TO", "Turkey": "TR", "Trinidad and Tobago": "TT", "Tuvalu": "TV", "Taiwan, Province of China": "TW", "Tanzania, United Republic of": "TZ", "Ukraine": "UA", "Uganda": "UG", "United States Minor Outlying Islands": "UM", "United States": "US", "Uruguay": "UY", "Uzbekistan": "UZ", "Holy See (Vatican City State)": "VA", "Saint Vincent and the Grenadines": "VC", "Venezuela": "VE", "Virgin Islands, British": "VG", "Virgin Islands, U.S.": "VI", "Vietnam": "VN", "Vanuatu": "VU", "Wallis and Futuna": "WF", "Samoa": "WS", "Yemen": "YE", "Mayotte": "YT", "South Africa": "ZA", "Zambia": "ZM", "Zimbabwe": "ZW" } export const inverted_countries_abreviations = Object.fromEntries( Object.entries(countries_abreviations).map(([key, value]) => [value, key]) ); ================================================ FILE: src/js/backend/scriptUtils/createStaffUtils.js ================================================ import { queryDB } from "../dbManager"; import { countries_abreviations, inverted_countries_abreviations } from "./countries.js"; const DRIVER_STAT_IDS = [2, 3, 4, 5, 6, 7, 8, 9, 10]; const STAFF_STAT_IDS = { 1: [0, 1, 14, 15, 16, 17], 2: [13, 25, 43], 3: [19, 20, 26, 27, 28, 29, 30, 31], 4: [11, 22, 23, 24] }; const STAFF_TYPE_NAMES = { 0: "Drivers", 1: "Technical Chiefs", 2: "Race Engineers", 3: "H. of Aerodynamics", 4: "Sporting Directors" }; export function fetchRandomStaffDraft(typeStaffRaw, gameYear = "24") { const typeStaff = normalizeStaffType(typeStaffRaw); const nationality = pickRandomNationality(gameYear); const gender = randomInt(0, 1); const firstNameLocKey = pickRandomForename(gender, nationality.staffNameLocale); const lastNameLocKey = pickRandomSurname(nationality.staffNameLocale); const firstName = extractNameToken(firstNameLocKey); const lastName = extractNameToken(lastNameLocKey); const { age, retirementAge } = buildAgeDetails(typeStaff); const stats = buildRandomStats(typeStaff); const driverCode = typeStaff === 0 ? buildDriverCode(firstName, lastName) : ""; const driverNumber = typeStaff === 0 ? pickAvailableDriverNumber() : 0; const statsArray = (typeStaff === 0) ? [...stats.values, stats.improvability, stats.aggression] : stats.values; return { draftId: `draft-${Date.now()}-${randomInt(1000, 9999)}`, draft: true, typeStaff: String(typeStaff), typeName: STAFF_TYPE_NAMES[typeStaff], gender, firstName, lastName, firstNameLocKey, lastNameLocKey, name: `${firstName} ${lastName}`.trim(), nationality: nationality.code, countryId: nationality.countryId, countryName: nationality.name, staffNameLocale: nationality.staffNameLocale, stats: statsArray.join(" "), statsArray, age, retirement_age: retirementAge, marketability: typeStaff === 0 ? stats.marketability : undefined, improvability: typeStaff === 0 ? stats.improvability : undefined, aggression: typeStaff === 0 ? stats.aggression : undefined, driver_number: driverNumber, wants1: 0, superlicense: typeStaff === 0 ? 1 : 0, driver_code: driverCode, isRetired: 0, race_formula: 4, teamid: 0, mentality0: gameYear === "24" ? 2 : -1, mentality1: gameYear === "24" ? 2 : -1, mentality2: gameYear === "24" ? 2 : -1, global_mentality: gameYear === "24" ? 59 : -1 }; } export function fetchRandomDraftForename(genderRaw, staffNameLocaleRaw) { const gender = Number(genderRaw); const staffNameLocale = Number(staffNameLocaleRaw); const genderText = gender === 1 ? "Female" : "Male"; const firstNameLocKey = queryDB(` SELECT LocKey FROM Staff_ForenamePool WHERE LocKey LIKE ? AND Locale = ? ORDER BY RANDOM() LIMIT 1 `, [`%StaffName_Forename_${genderText}_%`, staffNameLocale], "singleValue"); const lastNameLocKey = pickRandomSurname(staffNameLocale); return { firstNameLocKey, firstName: extractNameToken(firstNameLocKey), lastNameLocKey, lastName: extractNameToken(lastNameLocKey) }; } export function fetchCountryLocaleForCode(codeRaw) { const code = String(codeRaw || "").toUpperCase(); const nationalityName = inverted_countries_abreviations[code] || ""; const key = nationalityName.replace(/\s+/g, ""); const row = queryDB(` SELECT CountryID, Name, StaffNameLocale FROM Countries WHERE Name LIKE ? LIMIT 1 `, [`%[Nationality_${key}]%`], "singleRow"); return { code, countryId: row?.[0] ?? null, countryName: nationalityName, staffNameLocale: row?.[2] ?? null }; } function normalizeStaffType(typeStaffRaw) { const typeStaff = Number(typeStaffRaw); if (!Number.isInteger(typeStaff) || typeStaff < 0 || typeStaff > 4) { throw new Error(`Invalid staff type: ${typeStaffRaw}`); } return typeStaff; } function buildAgeDetails(typeStaff) { let minAge = 18; let maxAge = 38; let minRetirementGap = 4; let maxRetirementAge = 46; if (typeStaff === 1 || typeStaff === 3 || typeStaff === 4) { minAge = 32; maxAge = 66; minRetirementGap = 5; maxRetirementAge = 75; } else if (typeStaff === 2) { minAge = 24; maxAge = 60; minRetirementGap = 5; maxRetirementAge = 70; } const age = randomInt(minAge, maxAge); const retirementAge = randomInt(age + minRetirementGap, maxRetirementAge); return { age, retirementAge }; } function buildRandomStats(typeStaff) { const statIDs = typeStaff === 0 ? DRIVER_STAT_IDS : STAFF_STAT_IDS[typeStaff]; const base = pickBaseRating(typeStaff); const spread = typeStaff === 0 ? 12 : 10; const values = statIDs.map(() => statAroundBase(base, spread, { min: 64, max: 100 })); return { values, improvability: typeStaff === 0 ? statAroundBase(base, 22, { min: 0, max: 100 }) : undefined, aggression: typeStaff === 0 ? statAroundBase(base, 22, { min: 0, max: 100 }) : undefined, marketability: typeStaff === 0 ? statAroundBase(base, 25, { min: 0, max: 100 }) : undefined }; } function pickRandomForename(gender, staffNameLocale = null) { const genderText = gender === 1 ? "Female" : "Male"; const locKey = queryDB(` SELECT LocKey FROM Staff_ForenamePool WHERE LocKey LIKE ? AND Locale = ? ORDER BY RANDOM() LIMIT 1 `, [`%StaffName_Forename_${genderText}_%`, staffNameLocale], "singleValue"); if (locKey) return locKey; return "[STRING_LITERAL:Value=|New|]"; } function pickRandomSurname(staffNameLocale = null) { const locKey = queryDB(` SELECT LocKey FROM Staff_SurnamePool WHERE LocKey IS NOT NULL AND Locale = ? ORDER BY RANDOM() LIMIT 1 `, [staffNameLocale], "singleValue"); if (locKey) return locKey; return "[STRING_LITERAL:Value=|Person|]"; } function pickRandomNationality(gameYear) { let year = String(gameYear || "").trim(); if (year === "2024") year = "24"; if (year === "2023") year = "23"; if (year === "24") { const row = queryDB(` SELECT CountryID, Name, StaffNameLocale FROM Countries WHERE Name LIKE '%[Nationality_%' ORDER BY RANDOM() LIMIT 1 `, [], "singleRow"); const match = String(row?.[1] || "").match(/(?<=\[Nationality_)[^\]]+/); const nationalityName = match ? match[0].replace(/(? c + c).join(""); } const r = parseInt(hex.substring(0, 2), 16); const g = parseInt(hex.substring(2, 4), 16); const b = parseInt(hex.substring(4, 6), 16); const a = 255; return ((a << 24) | (r << 16) | (g << 8) | b) >>> 0; } /** * Convierte un color hex a uint32 en formato ARGB (AARRGGBB) como en tu DB. * Acepta: #RGB, #RRGGBB, #AARRGGBB (también sin #). * * @param {string} hex - Color en hex. * @param {number} [defaultAlpha=255] - Alpha (0-255) si el hex NO incluye alpha. * @returns {number} - Entero uint32 (0..4294967295) */ export function hexToDbArgb(hex, defaultAlpha = 255) { if (typeof hex !== "string") throw new TypeError("hex must be a string"); let s = hex.trim().replace(/^#/, ""); // #RGB -> #RRGGBB if (s.length === 3) { s = s.split("").map(ch => ch + ch).join(""); } if (![6, 8].includes(s.length) || !/^[0-9a-fA-F]+$/.test(s)) { throw new Error(`Invalid hex color: "${hex}"`); } let a, r, g, b; if (s.length === 6) { a = clampByte(defaultAlpha); r = parseInt(s.slice(0, 2), 16); g = parseInt(s.slice(2, 4), 16); b = parseInt(s.slice(4, 6), 16); } else { a = parseInt(s.slice(0, 2), 16); r = parseInt(s.slice(2, 4), 16); g = parseInt(s.slice(4, 6), 16); b = parseInt(s.slice(6, 8), 16); } return (((a << 24) | (r << 16) | (g << 8) | b) >>> 0); } function clampByte(n) { const x = Number(n); return Math.max(0, Math.min(255, Math.round(x))); } export function getDate() { const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); return daySeason } /** * Verifica si el archivo de guardado es de un año específico. * @returns {Array} [ "23" o "24", TeamName, primaryColor, secondaryColor ] */ export function checkYearSave() { // Ver si existe la tabla Countries_RaceRecord const row = queryDB(` SELECT name FROM sqlite_master WHERE type='table' AND name='Countries_RaceRecord' `, [], 'singleRow'); if (!row) { // No existe la tabla -> asumo que es "23" return ["23", null, null, null]; } // Si existe, entonces busco TeamNameLocKey del TeamID=32 const nameValue = queryDB(` SELECT TeamNameLocKey FROM Teams WHERE TeamID = 32 `, [], 'singleValue'); if (!nameValue) { // No hay valor -> devuelvo "24" sin datos return ["24", null, null, null]; } // Extraer nombre const match = nameValue.match(/\[STRING_LITERAL:Value=\|(.*?)\|\]/); let name = null, primaryColor = null, secondaryColor = null; if (match) { name = match[1]; // Busco los colores const primaryColorRow = queryDB(` SELECT Colour FROM Teams_Colours WHERE TeamID = 32 AND ColourID = 0 `, [], 'singleRow'); const secondaryColorRow = queryDB(` SELECT Colour FROM Teams_Colours WHERE TeamID = 32 AND ColourID = 1 `, [], 'singleRow'); if (primaryColorRow) { primaryColor = argbToHex(primaryColorRow[0]); } if (secondaryColorRow) { secondaryColor = argbToHex(secondaryColorRow[0]); } } return ["24", name, primaryColor, secondaryColor]; } export function fetchNationality(driverID, gameYear) { let year = String(gameYear || "").trim(); if (year === "2024") year = "24"; if (year === "2023") year = "23"; if (year === "24") { const countryID = queryDB(` SELECT CountryID FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleValue'); if (!countryID) return ""; const countryName = queryDB(` SELECT Name FROM Countries WHERE CountryID = ? `, [countryID], 'singleValue'); if (!countryName) return ""; const match = countryName.match(/(?<=\[Nationality_)[^\]]+/); if (match) { const nat = match[0]; const natName = nat.replace(/(? 0) { juniorFormulaInfo.teamId = juniorContracts[0][0]; juniorFormulaInfo.posInTeam = juniorContracts[0][1]; } return juniorFormulaInfo; } export function fetchEngines() { const statsIds = [6, 10, 11, 12, 14, 15]; const enginesList = []; let newEngineIds = queryDB(` SELECT engineID FROM Custom_Engines_List`, [], 'allRows'); newEngineIds = newEngineIds.map(row => row[0]); let newErsIds = newEngineIds.map(id => id + 1); let newGearboxesIds = newEngineIds.map(id => id + 2); for (let i = 0; i < newEngineIds.length; i++) { let resultDict = {}; // Obtener valores de stats for (const stat of statsIds) { const statResult = queryDB(` SELECT partStat, unitValue FROM Custom_Engines_Stats WHERE designId = ? AND partStat = ? `, [newEngineIds[i], stat], 'singleRow'); if (statResult) { resultDict[statResult[0]] = statResult[1]; } } // Obtener valor de ERS const ersResult = queryDB(` SELECT UnitValue FROM Custom_Engines_Stats WHERE designId = ? AND partStat = 15 `, [newErsIds[i]], 'singleValue'); if (ersResult !== null) { resultDict[18] = ersResult; } // Obtener valor de gearbox const gearboxResult = queryDB(` SELECT UnitValue FROM Custom_Engines_Stats WHERE designId = ? AND partStat = 15 `, [newGearboxesIds[i]], 'singleValue'); if (gearboxResult !== null) { resultDict[19] = gearboxResult; } const engineName = queryDB(` SELECT name FROM Custom_Engines_List WHERE engineID = ? `, [newEngineIds[i]], 'singleValue'); // Añadir la información del motor a la lista enginesList.push([newEngineIds[i], resultDict, engineName]); } const engineAllocations = queryDB(` SELECT * FROM Custom_Engine_Allocations `, [], 'allRows'); return [enginesList, engineAllocations]; } export function ensureCustomEngineProgressionTable() { queryDB(` CREATE TABLE IF NOT EXISTS Custom_Engine_Progression ( SeasonID INTEGER NOT NULL, RaceID INTEGER NOT NULL, EngineID INTEGER NOT NULL, Power REAL NOT NULL, Source TEXT NULL, PRIMARY KEY (SeasonID, RaceID, EngineID) ) `, [], 'run'); queryDB(` CREATE INDEX IF NOT EXISTS idx_Custom_Engine_Progression_Season_Engine_Race ON Custom_Engine_Progression (SeasonID, EngineID, RaceID) `, [], 'run'); } function getNextSnapshotRaceIdForSeason(seasonId) { if (!Number.isFinite(Number(seasonId))) { return null; } const nextRaceIdRaw = queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? AND State != 2 ORDER BY Day ASC LIMIT 1 `, [seasonId], 'singleValue'); if (nextRaceIdRaw !== null && nextRaceIdRaw !== undefined) { const nextRaceId = Number(nextRaceIdRaw); return Number.isFinite(nextRaceId) && nextRaceId > 0 ? nextRaceId : null; } const maxRaceIdRaw = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ?`, [seasonId], 'singleValue'); const maxRaceId = Number(maxRaceIdRaw) || 0; return maxRaceId > 0 ? (maxRaceId + 1) : null; } export function snapshotEnginePowerProgression(engineIdsRaw, source, seasonIdRaw = null, raceIdRaw = null) { ensureCustomEngineProgressionTable(); const seasonId = Number(seasonIdRaw) || Number(queryDB(`SELECT CurrentSeason FROM Player_State`, [], 'singleValue')) || null; if (!seasonId) return { ok: false, error: "Missing season id" }; const raceId = Number(raceIdRaw) || getNextSnapshotRaceIdForSeason(seasonId); if (!Number.isFinite(raceId) || raceId <= 0) return { ok: false, error: "Missing race id" }; const engineIds = (engineIdsRaw || []) .map((id) => Number(id)) .filter((id) => Number.isFinite(id) && id > 0); if (!engineIds.length) return { ok: true, seasonId, raceId, inserted: 0 }; const placeholders = engineIds.map(() => '?').join(', '); const powerRows = queryDB(` SELECT engineId, unitValue FROM Custom_Engines_Stats WHERE designId = engineId AND partStat = 10 AND engineId IN (${placeholders}) `, engineIds, 'allRows') || []; let inserted = 0; for (const row of powerRows) { const engineId = Number(row?.[0]); const power = Number(row?.[1]); if (!engineId || !Number.isFinite(power)) continue; queryDB(` INSERT OR IGNORE INTO Custom_Engine_Progression (SeasonID, RaceID, EngineID, Power, Source) VALUES (?, ?, ?, ?, ?) `, [seasonId, raceId, engineId, power, source || null], 'run'); inserted += 1; } return { ok: true, seasonId, raceId, inserted }; } export function fetchMentality(staffID) { // Obtengo todas las filas (morale es un array de arrays [[opinion],[opinion], ...]) const morale = queryDB(` SELECT Opinion FROM Staff_Mentality_AreaOpinions WHERE StaffID = ? `, [staffID], 'allRows'); // Obtengo un solo valor const globalMentality = queryDB(` SELECT Mentality FROM Staff_State WHERE StaffID = ? `, [staffID], 'singleValue'); return [morale, globalMentality]; } export function checkDrivesForTeam32(staffData) { // staffData = [ firstName, lastName, staffID, teamID, posInTeam, minContractType, retired, countContracts ] const contractRow = queryDB(` SELECT TeamID, PosInTeam FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = 32 `, [staffData[2]], 'singleRow'); if (contractRow) { return [ staffData[0], staffData[1], staffData[2], 32, contractRow[1], staffData[5], staffData[6], staffData[7] ]; } return staffData; } export function removeNumber(str) { if (str && /\d$/.test(str)) { return str.slice(0, -1); } return str; } export function formatNamesAndFetchStats(nameData, type) { // nameData: [ FirstName, LastName, StaffID, teamId, positionInTeam, minContractType, retired, countContracts ] let firstName = ""; let lastName = ""; // Extract firstName if (!nameData[0].includes("STRING_LITERAL")) { const m = nameData[0].match(/StaffName_Forename_(?:Male|Female)_(\w+)/); firstName = m ? removeNumber(m[1]) : ""; } else { const m = nameData[0].match(/\|([^|]+)\|/); firstName = m ? m[1] : ""; } // Extract lastName if (!nameData[1].includes("STRING_LITERAL")) { const m = nameData[1].match(/StaffName_Surname_(\w+)/); lastName = m ? removeNumber(m[1]) : ""; } else { const m = nameData[1].match(/\|([^|]+)\|/); lastName = m ? m[1] : ""; } const formattedName = `${firstName} ${lastName}`; let teamId = nameData[3] ?? 0; let positionInTeam = nameData[4] ?? 0; // para drivers que tienen minContractType != 0 (p.ej. reservas) if (type === "driver" && nameData[5] !== 0) { teamId = 0; positionInTeam = 0; } let baseResult; if (type === "driver") { // [nombre, staffID, teamID, posInTeam, retired] baseResult = [formattedName, nameData[2], teamId, positionInTeam, nameData[6]]; } else { // staff normal baseResult = [formattedName, nameData[2], teamId, positionInTeam]; } // Buscamos stats if (type === "driver") { const statsRows = queryDB(` SELECT Val FROM Staff_PerformanceStats WHERE StaffID = ? AND StatID BETWEEN 2 AND 10 `, [nameData[2]], 'allRows'); let stats = statsRows; if (!stats || !stats.length) { // si no hay stats, por defecto 50 stats = Array(9).fill([50]); } const extraRow = queryDB(` SELECT Improvability, Aggression FROM Staff_DriverData WHERE StaffID = ? `, [nameData[2]], 'singleRow'); // Concatenamos: baseResult + stats + extraRow // stats es array de arrays: [[val],[val],...] // mapeamos para quedarnos con stats[i][0] return baseResult.concat( stats.map(s => s[0]), extraRow ?? [] ); } // staff normal let statIDs = []; if (type === "staff1") { statIDs = [0, 1, 14, 15, 16, 17]; } else if (type === "staff2") { statIDs = [13, 25, 43]; } else if (type === "staff3") { statIDs = [19, 20, 26, 27, 28, 29, 30, 31]; } else if (type === "staff4") { statIDs = [11, 22, 23, 24]; } if (statIDs.length) { const statsRows = queryDB(` SELECT Val FROM Staff_PerformanceStats WHERE StaffID = ? AND StatID IN (${statIDs.join(",")}) `, [nameData[2]], 'allRows'); return baseResult.concat(statsRows.map(s => s[0])); } // Si no entra en esos casos, simplemente devolvemos baseResult return baseResult; } export function fetchDriverRetirement(driverID) { const playerRow = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); let day = 0, currentSeason = 0; if (playerRow) { [day, currentSeason] = playerRow; } else { console.warn("No se encontraron datos en Player_State."); } const retirementAge = queryDB(` SELECT RetirementAge FROM Staff_GameData WHERE StaffID = ? `, [driverID], 'singleValue'); const dob = queryDB(` SELECT DOB FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleValue'); const age = (dob != null) ? Math.floor((day - dob) / 365.25) : 0; return [retirementAge, age]; } export function fetchDriverCode(driverID) { let code = queryDB(` SELECT DriverCode FROM Staff_DriverData WHERE StaffID = ? `, [driverID], 'singleValue'); if (!code) return ""; if (!code.includes("STRING_LITERAL")) { const m = code.match(/\[DriverCode_(...)\]/); code = m ? m[1] : ""; } else { const m = code.match(/\[STRING_LITERAL:Value=\|(...)\|\]/); code = m ? m[1] : ""; } return code.toUpperCase(); } export function fetchYear() { const row = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!row) { console.warn("No data found in Player_State."); return 0; } // Devolvemos CurrentSeason (row[1]) return row[1]; } export function fetchDriverNumberDetails(driverID) { let currentNumber = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder = ? `, [driverID], 'singleValue'); if (currentNumber == null) { // Si no tiene número, ver si hay libres const available = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder IS NULL `, [], 'allRows'); if (!available.length) { currentNumber = 0; } else { // Elige uno aleatorio const randomIdx = Math.floor(Math.random() * available.length); currentNumber = available[randomIdx][0]; } } // Quiere usar número de campeón? const wantsChampion = queryDB(` SELECT WantsChampionDriverNumber FROM Staff_DriverData WHERE StaffID = ? `, [driverID], 'singleValue'); return [currentNumber, wantsChampion]; } export function fetchRaceFormula(driverID) { const category = queryDB(` SELECT MAX( CASE WHEN (TeamID <= 10 OR TeamID = 32) THEN 1 WHEN TeamID BETWEEN 11 AND 21 THEN 2 WHEN TeamID BETWEEN 22 AND 31 THEN 3 ELSE 4 END ) AS Cat FROM Staff_Contracts WHERE ContractType = 0 AND StaffID = ? `, [driverID], 'singleValue'); // Por defecto 4 si no existe return category ?? 4; } export function fetchMarketability(driverID) { return queryDB(` SELECT Marketability FROM Staff_DriverData WHERE StaffID = ? `, [driverID], 'singleValue'); } export function fetchSuperlicense(driverID) { return queryDB(` SELECT HasSuperLicense FROM Staff_DriverData WHERE StaffID = ? `, [driverID], 'singleValue'); } export function fetchDrivers(gameYear) { const rows = queryDB(` SELECT DISTINCT bas.FirstName, bas.LastName, bas.StaffID, con.TeamID, con.PosInTeam, MIN(con.ContractType) AS MinContractType, gam.Retired, COUNT(*) FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID LEFT JOIN Staff_Contracts con ON dri.StaffID = con.StaffID LEFT JOIN Staff_GameData gam ON dri.StaffID = gam.StaffID GROUP BY gam.StaffID ORDER BY con.TeamID; `, [], 'allRows'); const formattedData = []; for (let driver of rows) { // Si driver[7] > 1 => hay más de un contrato if (driver[7] > 1) { driver = checkDrivesForTeam32(driver); } const driverID = driver[2]; // Ignoramos placeholders if (driver[0] === "Placeholder") { continue; } // Format + stats const result = formatNamesAndFetchStats(driver, "driver"); // Extra info const [retirementAge, age] = fetchDriverRetirement(driverID); let raceFormula = fetchRaceFormula(driverID) || 4; const [driverNumber, wants1] = fetchDriverNumberDetails(driverID); const superlicense = fetchSuperlicense(driverID); const futureTeam = fetchForFutureContract(driverID); const juniorContracts = fetchJuniorContracts(driverID); const driverCode = fetchDriverCode(driverID); const nationality = fetchNationality(driverID, gameYear); // result es array, lo convertimos a objeto para mayor claridad const data = { ...result }; data.driver_number = driverNumber; data.wants1 = wants1; data.retirement_age = retirementAge; data.age = age; data.superlicense = superlicense; data.race_formula = raceFormula; data.team_future = futureTeam; data.team_junior = juniorContracts; data.driver_code = driverCode; data.nationality = nationality; // Datos específicos para 2024 if (gameYear === "24") { const [morale, gMentality] = fetchMentality(driverID); data.global_mentality = gMentality ?? null; // morale es array de arrays. Ejemplo: [ [op1], [op2], [op3] ] if (morale.length >= 3) { data.mentality0 = morale[0][0]; data.mentality1 = morale[1][0]; data.mentality2 = morale[2][0]; } const market = fetchMarketability(driverID); data.marketability = market ?? 0; } formattedData.push(data); } return formattedData; } export function fetchStaff(gameYear) { const rows = queryDB(` SELECT DISTINCT bas.FirstName, bas.LastName, bas.StaffID, con.TeamID, gam.StaffType FROM Staff_GameData gam JOIN Staff_BasicData bas ON gam.StaffID = bas.StaffID LEFT JOIN Staff_Contracts con ON bas.StaffID = con.StaffID AND (con.ContractType = 0 OR con.ContractType IS NULL) WHERE gam.StaffType != 0 ORDER BY CASE WHEN con.TeamID IS NULL THEN 1 ELSE 0 END, con.TeamID `, [], 'allRows'); if (!rows.length) { console.warn("No staff data found."); return []; } const formattedData = []; for (let staff of rows) { // staff = [ FirstName, LastName, StaffID, TeamID, StaffType ] if (staff[0] === "Placeholder") { continue; } const staffID = staff[2]; const staffType = `staff${staff[4]}`; const result = formatNamesAndFetchStats(staff, staffType); const [retirementAge, age] = fetchDriverRetirement(staffID); let raceFormula = fetchRaceFormula(staffID) || 4; const futureTeam = fetchForFutureContract(staffID); const nationality = fetchNationality(staffID, gameYear); const isRetired = queryDB(` SELECT Retired FROM Staff_GameData WHERE StaffID = ? `, [staffID], 'singleValue'); const data = { ...result }; data.retirement_age = retirementAge; data.age = age; data.race_formula = raceFormula; data.team_future = futureTeam; data.nationality = nationality; data.is_retired = isRetired ?? 0; if (gameYear === "24") { const [morale, gMentality] = fetchMentality(staffID); data.global_mentality = gMentality ?? -1; if (morale.length >= 3) { data.mentality0 = morale[0][0] ?? -1; data.mentality1 = morale[1][0] ?? -1; data.mentality2 = morale[2][0] ?? -1; } else { data.mentality0 = -1; data.mentality1 = -1; data.mentality2 = -1; } } formattedData.push(data); } return formattedData; } export function fetchDriversPerYear(year) { // Construimos la consulta SQL const sql = ` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID FROM Staff_BasicData bas JOIN Races_Results res ON bas.StaffID = res.DriverID WHERE Season = ? GROUP BY bas.FirstName, bas.LastName, bas.StaffID, res.TeamID ORDER BY res.TeamID `; // Obtenemos todas las filas (array de objetos o tuplas) const drivers = queryDB(sql, [year], 'allRows') || []; // Formateamos cada fila como quieras (equivalente a "format_names_simple") const formattedTuples = drivers.map(row => formatNamesSimple(row)); return formattedTuples; } export function formatNamesSimple(name) { let nombre = ""; let apellido = ""; // Si no contiene "STRING_LITERAL", buscamos "StaffName_Forename_(Male|Female)_(...)". if (!name[0].includes("STRING_LITERAL")) { const nombrePattern = /StaffName_Forename_(Male|Female)_(\w+)/; const match = name[0].match(nombrePattern); if (match) { // Asumiendo que tienes un método removeNumber similar al de Python nombre = removeNumber(match[2]); } else { nombre = ""; } } else { // De lo contrario, buscamos la parte entre "| ... |" const pattern = /\|([^|]+)\|/; const match = name[0].match(pattern); if (match) { nombre = match[1]; } else { nombre = ""; } } // Repetimos la lógica para el apellido if (!name[1].includes("STRING_LITERAL")) { const apellidoPattern = /StaffName_Surname_(\w+)/; const match = name[1].match(apellidoPattern); if (match) { apellido = removeNumber(match[1]); } else { apellido = ""; } } else { const pattern = /\|([^|]+)\|/; const match = name[1].match(pattern); if (match) { apellido = match[1]; } else { apellido = ""; } } // Construimos el nombre completo const nameFormatted = `${nombre} ${apellido}`.trim(); // El TeamID (índice 3 en el array). Si es nulo/indefinido, lo ponemos a 0 const teamId = name[3] != null ? name[3] : 0; // Devolvemos la misma estructura que en Python: (Nombre Formateado, DriverID, TeamID) return [nameFormatted, name[2], teamId]; } function getSeasonRaceIds(season) { return (queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? ORDER BY RaceID ASC `, [season], 'allRows') || []).map(r => Number(r[0])); } export function buildPerRaceTeamRankContext(seasonResults, raceIds, season) { const cacheKey = `teamRanks:${season}:prev=false:pts=false`; const cached = _standingsCache.get(cacheKey); if (cached) return cached; const perRaceTeamPoints = new Map(); for (const dr of (seasonResults || [])) { const races = dr.races || []; for (const r of races) { const raceId = Number(r.raceId); const teamId = Number(r.teamId); const pts = Math.max(0, Number(r.points) || 0); const spts = (r.sprintPoints != null && Number(r.sprintPoints) !== -1) ? Number(r.sprintPoints) : 0; const total = pts + spts; if (!perRaceTeamPoints.has(raceId)) perRaceTeamPoints.set(raceId, new Map()); const m = perRaceTeamPoints.get(raceId); m.set(teamId, (m.get(teamId) || 0) + total); } } const perRaceRank = new Map(); const cumPoints = new Map(); const orderedRaceIds = [...(raceIds || [])].map(Number).sort((a, b) => a - b); for (const rid of orderedRaceIds) { const standingsArr = [...cumPoints.entries()] .map(([teamId, points]) => ({ teamId: Number(teamId), points: Number(points) })) .sort((a, b) => b.points - a.points); const rankMap = new Map(); standingsArr.forEach((t, idx) => rankMap.set(t.teamId, idx + 1)); perRaceRank.set(rid, rankMap); const thisRacePts = perRaceTeamPoints.get(rid); if (thisRacePts) { for (const [teamId, pts] of thisRacePts.entries()) { cumPoints.set(teamId, (cumPoints.get(teamId) || 0) + pts); } } } _standingsCache.set(cacheKey, perRaceRank); return perRaceRank; } export function buildPerRaceTeamRankContext_OLD(seasonResults, raceIds, season) { // raceId -> Map(teamId -> rank 1..10) const perRace = new Map(); for (const raceId of raceIds) { // reconstruye standings "hasta" esa carrera const { teamStandings } = rebuildStandingsUntilCached( season, seasonResults, raceId, false, false ); const rankMap = new Map(); teamStandings.forEach((t, idx) => rankMap.set(Number(t.teamId), idx + 1)); perRace.set(Number(raceId), rankMap); } return perRace; } function applyDoDFlagsToSeasonResults(seasonResults, dodMap) { for (const dr of seasonResults) { const driverId = Number(dr.driverID?.[0] ?? dr.DriverID?.[0] ?? dr.driverId ?? dr.id ?? -1); for (const r of (dr.races || [])) { const rid = Number(r.raceId ?? r.RaceID ?? r.id); r.driverOfTheDay = dodMap.get(rid) === driverId; } } return seasonResults; } export function getDotDWinnersMap(season) { const rows = queryDB(` SELECT RaceID, DriverID FROM Custom_DriverOfTheDay_Ranking WHERE Season = ? AND Rank = 1 `, [season], 'allRows') || []; const m = new Map(); for (const [raceId, driverId] of rows) { m.set(Number(raceId), Number(driverId)); } return m; } function computeSeasonDriverOfTheDay(seasonResults, season) { ensureCustomDoDRankingTable(); // A) contexto por carrera const raceIds = getSeasonRaceIds(season).map(Number); const perRaceTeamRank = buildPerRaceTeamRankContext(seasonResults, raceIds, season); // B) ganadores ya cacheados (Rank=1) const winnersMap = getDotDWinnersMap(season); // Map // C) carreras faltantes const missing = raceIds.filter(rid => !winnersMap.has(rid)); if (missing.length > 0) { // Trae solo lo necesario const rows = queryDB(` SELECT RaceID, DriverID, TeamID, StartingPos, FinishingPos, DNF, Time, Laps FROM Races_Results WHERE Season = ${season} AND RaceID IN (${missing.join(',')}) `, [], 'allRows') || []; // Agrupar por carrera en el formato que ya usas const byRace = new Map(); for (const [raceId, driverId, teamId, startPos, finishPos, dnf, time, laps] of rows) { const r = Number(raceId); if (!byRace.has(r)) byRace.set(r, []); byRace.get(r).push([ null, null, Number(driverId), Number(teamId), Number(finishPos), Number(startPos), null, Number(dnf), null, null, Number(time), Number(laps) ]); } // Calcular leaderboard y guardar top-3 for (const raceId of missing) { const raceRows = (byRace.get(raceId) || []).sort((a, b) => Number(a[4]) - Number(b[4])); const ctx = { teamRankByTeamId: perRaceTeamRank.get(raceId) || new Map() }; const leaderboard = computeDriverOfTheDayLeaderboardFromRows(raceRows, raceId, ctx); if (leaderboard?.length) { upsertDoDRanking(season, raceId, leaderboard, /*topN*/ 3); winnersMap.set(raceId, Number(leaderboard[0].driverId)); } } } // D) aplicar flags como ya hacías const dodMap = winnersMap; // Map const enriched = applyDoDFlagsToSeasonResults(seasonResults, dodMap); enriched._driverOfTheDayMap = dodMap; return enriched; } export function fetchSeasonResults( yearSelected, isCurrentYear = true, fetchDriverOfTheDay = false, formula = 1 ) { const drivers = queryDB(` SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = ? AND SeasonID = ? ORDER BY Position `, [formula, yearSelected], 'allRows') || []; const seasonResults = []; for (const row of drivers) { const driverID = row[0]; const driverRes = fetchOneDriverSeasonResults([driverID], [yearSelected], isCurrentYear, formula); if (driverRes) seasonResults.push(driverRes); } if (!fetchDriverOfTheDay || Number(formula) !== 1) { return seasonResults; } const resultsWithDoD = computeSeasonDriverOfTheDay(seasonResults, yearSelected); return resultsWithDoD; } export function fetchQualiResults(yearSelected) { const drivers = queryDB(` SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ? `, [yearSelected], 'allRows') || []; const seasonResults = []; drivers.forEach((row) => { const driverID = row[0]; const driverRes = fetchOneDriverQualiResults([driverID], [yearSelected]); if (driverRes) { seasonResults.push(driverRes); } }); return seasonResults; } export function fetchTeamsStandings(year, formula = 1) { return queryDB(` SELECT TeamID, Position FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = ? ORDER BY Position `, [year, formula], 'allRows') || []; } export function fetchTeamsStandingsWithPoints(year, formula = 1) { return queryDB(` SELECT TeamID, Position, Points FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = ? ORDER BY Position `, [year, formula], 'allRows') || []; } function fetchTeamSeasonCountsFromRaceResults(year, extraWhereSql) { const isCreateATeam = !!getGlobals().isCreateATeam; const teamFilterSql = isCreateATeam ? `(rr.TeamID BETWEEN 1 AND 10 OR rr.TeamID = 32)` : `(rr.TeamID BETWEEN 1 AND 10)`; const rows = queryDB(` SELECT rr.TeamID, COUNT(*) AS Cnt FROM Races_Results rr WHERE rr.Season = ? AND ${teamFilterSql} AND ${extraWhereSql} GROUP BY rr.TeamID ORDER BY Cnt DESC, rr.TeamID ASC `, [year], 'allRows') || []; return rows.map(r => ({ teamId: r[0], value: r[1] ?? 0, })); } export function fetchTeamSeasonWinsTotals(year, formula = 1) { if (Number(formula) !== 1) return []; return fetchTeamSeasonCountsFromRaceResults(year, `rr.FinishingPos = 1`); } export function fetchTeamSeasonPodiumsTotals(year, formula = 1) { if (Number(formula) !== 1) return []; return fetchTeamSeasonCountsFromRaceResults(year, `rr.FinishingPos BETWEEN 1 AND 3`); } export function fetchTeamSeasonPolesTotals(year, formula = 1) { if (Number(formula) !== 1) return []; return fetchTeamSeasonCountsFromRaceResults(year, `rr.StartingPos = 1`); } export function fetchDriversStandings(year, formula = 1) { if (Number(formula) === 1) { const rows = queryDB(` SELECT ds.DriverID, ds.Position, ds.Points, COALESCE(( SELECT rr.TeamID FROM Races_Results rr JOIN Races r ON r.RaceID = rr.RaceID WHERE rr.DriverID = ds.DriverID AND r.SeasonID = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID FROM Races_DriverStandings ds WHERE ds.SeasonID = ? AND ds.RaceFormula = ? AND EXISTS ( SELECT 1 FROM Races_Results rr2 WHERE rr2.Season = ? AND rr2.DriverID = ds.DriverID AND rr2.FinishingPos > 0 AND rr2.FinishingPos != 99 LIMIT 1 ) ORDER BY ds.Position `, [year, year, formula, year], 'allRows'); const formatted = rows.map(r => { let names = queryDB(` SELECT FirstName, LastName, 1, 1 FROM Staff_BasicData WHERE StaffID = ? `, [r[0]], 'singleRow'); let name = formatNamesSimple(names); return { DriverID: r[0], DriverName: name[0], Position: r[1], Points: r[2], TeamID: r[3] } }); return formatted; } return queryDB(` SELECT ds.DriverID, ds.Position, ds.Points, COALESCE(( SELECT fr.TeamID FROM Races_FeatureRaceResults fr JOIN Races r ON r.RaceID = fr.RaceID WHERE fr.DriverID = ds.DriverID AND fr.SeasonID = ? AND fr.RaceFormula = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID FROM Races_DriverStandings ds WHERE ds.SeasonID = ? AND ds.RaceFormula = ? AND EXISTS ( SELECT 1 FROM Races_FeatureRaceResults fr2 WHERE fr2.SeasonID = ? AND fr2.RaceFormula = ? AND fr2.DriverID = ds.DriverID AND fr2.FinishingPos > 0 AND fr2.FinishingPos != 99 LIMIT 1 ) ORDER BY ds.Position `, [year, formula, year, formula, year, formula], 'allRows') || []; } export function fetchTeamsStandingsWithPositionChange(year, formula = 1) { return queryDB(` SELECT TeamID, Position, LastPositionChange FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = ? ORDER BY Position `, [year, formula], 'allRows') || []; } export function fetchPointsRegulations() { const pointScheme = queryDB(`SELECT CurrentValue FROM Regulations_Enum_Changes WHERE ChangeID = 7`, [], 'singleValue'); const twoBiggestPoints = queryDB(`SELECT Points FROM Regulations_NonTechnical_PointSchemes WHERE (PointScheme = ?) AND (RacePos = 1 OR RacePos = 2); `, [pointScheme], 'allRows'); const isLastraceDouble = queryDB(`SELECT CurrentValue FROM Regulations_Enum_Changes WHERE ChangeID = 8`, [], 'singleValue'); const fastestLapBonusPoint = queryDB(`SELECT CurrentValue FROM Regulations_Enum_Changes WHERE ChangeID = 9`, [], 'singleValue'); const poleBonusPoint = queryDB(`SELECT CurrentValue FROM Regulations_Enum_Changes WHERE ChangeID = 10`, [], 'singleValue'); const positionAndPointsRows = queryDB(`SELECT RacePos, Points FROM Regulations_NonTechnical_PointSchemes WHERE PointScheme = ?`, [pointScheme], 'allRows'); const res = { pointScheme: pointScheme, twoBiggestPoints: twoBiggestPoints, isLastraceDouble: isLastraceDouble, fastestLapBonusPoint: fastestLapBonusPoint, poleBonusPoint: poleBonusPoint, positionAndPoints: positionAndPointsRows } return res; } export function fetchOneTeamSeasonResults(team, year) { const teamID = team; const season = year; const drivers = queryDB(` SELECT DISTINCT DriverID FROM Races_Results WHERE Season = ? AND TeamID = ? `, [season, teamID], 'allRows') || []; const results = []; for (let driver of drivers) { const driverID = driver[0]; const driverResults = fetchOneDriverSeasonResults(driverID, season); if (driverResults) { results.push(driverResults); } } return results; } export function fetchOneDriverSeasonResults(driver, year, isCurrentYear = true, formula = 1) { const driverID = Array.isArray(driver) ? driver[0] : driver; //if its not an array, take it as is, if it is, take first element const season = Array.isArray(year) ? year[0] : year; if (Number(formula) === 1) { const results = queryDB(` SELECT DriverID, TeamID, FinishingPos, Points FROM Races_Results WHERE Season = ? AND DriverID = ? `, [season, driverID], 'allRows') || []; if (results.length > 0) { const sprintResults = queryDB(` SELECT RaceID, FinishingPos, ChampionshipPoints FROM Races_SprintResults WHERE SeasonID = ? AND DriverID = ? AND RaceFormula = ? `, [season, driverID, formula], 'allRows') || []; const teamID = results[0][1]; const driverNameRow = queryDB(` SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleRow'); return formatSeasonResults( results, driverNameRow, teamID, driver, year, sprintResults, isCurrentYear, formula ); } return null; } else if (Number(formula) > 1) { const results = queryDB(` SELECT RaceID, TeamID, FinishingPos, ChampionshipPoints, 0, FastestLap FROM Races_FeatureRaceResults WHERE SeasonID = ? AND DriverID = ? AND RaceFormula = ? ORDER BY RaceID `, [season, driverID, formula], 'allRows') || []; if (results.length > 0) { const sprintResults = queryDB(` SELECT RaceID, FinishingPos, ChampionshipPoints FROM Races_SprintResults WHERE SeasonID = ? AND DriverID = ? AND RaceFormula = ? `, [season, driverID, formula], 'allRows') || []; const teamID = results[0][1]; const driverNameRow = queryDB(` SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleRow'); return formatSeasonResults( results, driverNameRow, teamID, driver, year, sprintResults, isCurrentYear, formula ); } return null; } } export function computeDriverOfTheDayFromRows(rows, raceId, opts = {}) { // const lb = computeDriverOfTheDayLeaderboardFromRows(rows, raceId, opts); //debug // console.table(lb.slice(0, 10)); // return lb[0]?.driverId || null; const dodId = computeDriverOfTheDayFromRows_fast(rows, raceId, opts); return dodId; } export function computeDriverOfTheDayFromRows_fast(rows, raceId, opts = {}) { if (!rows || !rows.length) return null; // --- constantes internas (sin pasar por opts si quieres fijarlas) --- const TEAM_WEIGHT = 0.4; const TEAM_BONUS_CAP = 4; const RANDOM_INTENSITY = 0.8; // ±0.4 const dominancePerGap = 1; // +1 punto por bloque de gap const dominanceBlock = 4; // cada 4s → 1 punto const dominanceMax = 10; // ranking de equipo const teamRankByTeamId = (opts.teamRankByTeamId instanceof Map) ? opts.teamRankByTeamId : new Map(); // bonus por dominancia del ganador (P1 vs P2 en misma vuelta) let p1GapBonus = 0; const p1 = rows.find(r => Number(r[4]) === 1 && Number(r[7]) === 0); const p2 = rows.find(r => Number(r[4]) === 2 && Number(r[7]) === 0); if (p1 && p2) { const p1Time = Number(p1[10]), p2Time = Number(p2[10]); const p1Laps = Number(p1[11]), p2Laps = Number(p2[11]); if (p1Laps === p2Laps) { const gapBehind = p2Time - p1Time; if (gapBehind > 0) { const blocks = Math.floor(gapBehind / dominanceBlock); p1GapBonus = Math.min(blocks * dominancePerGap, dominanceMax); } } } // posScore fijo const posScore = (finishingPos) => { if (finishingPos === 1) return 4; if (finishingPos === 2) return 2; if (finishingPos === 3) return 1; if (finishingPos > 13) return -10; if (finishingPos > 10) return -7; if (finishingPos > 8) return -2; return 0; }; // grid válido para expectedPos por equipo const validRows = rows.filter(r => Number(r[7]) !== 1 && Number(r[5]) > 0 && Number(r[4]) > 0 && Number(r[4]) !== 99); const gridSize = validRows.length; const gridFactor = gridSize > 0 ? (gridSize / 20) : 1; const teamBonus = (teamRank, finishingPos) => { const expectedPos = (2 * teamRank - 0.5) * gridFactor; // 2 coches por equipo const delta = expectedPos - finishingPos; // + si rinde mejor que lo esperado let bonus = delta * TEAM_WEIGHT; if (bonus > TEAM_BONUS_CAP) bonus = TEAM_BONUS_CAP; if (bonus < -TEAM_BONUS_CAP) bonus = -TEAM_BONUS_CAP; return bonus; }; // ganador en una sola pasada (sin arrays ni sort) let bestId = null, bestScore = -Infinity, bestFinishPos = 99; for (const row of rows) { const driverId = Number(row[2]); const teamId = Number(row[3]); const finishingPos = Number(row[4]); const startingPos = Number(row[5]); const dnf = Number(row[7]) === 1; if (dnf || startingPos <= 0 || finishingPos <= 0 || finishingPos === 99) continue; const gain = startingPos - finishingPos; const ps = posScore(finishingPos); const tr = Number(teamRankByTeamId.get(teamId)); const tb = teamBonus(tr, finishingPos); const dominanceBonus = (finishingPos === 1) ? p1GapBonus : 0; const rand = seededRandom(Number(raceId)); const randomOffset = (rand() - 0.5) * RANDOM_INTENSITY; const score = gain + ps + tb + dominanceBonus + randomOffset; // desempate por mejor posición final if ( score > bestScore || (score === bestScore && finishingPos < bestFinishPos) ) { bestScore = score; bestFinishPos = finishingPos; bestId = driverId; } } return bestId; } export function computeDriverOfTheDayLeaderboardFromRows(rows, raceId, opts = {}) { if (!rows || !rows.length) return []; raceId = Number(raceId); const teamRankByTeamId = (opts.teamRankByTeamId instanceof Map) ? opts.teamRankByTeamId : new Map(); const TEAM_WEIGHT = 0.4; const TEAM_BONUS_CAP = 4; const dominancePerGap = 1; const dominanceMax = 10; let p1GapBonus = 0; const p1 = rows.find(r => Number(r[4]) === 1 && Number(r[7]) === 0); const p2 = rows.find(r => Number(r[4]) === 2 && Number(r[7]) === 0); if (p1 && p2) { const p1Time = Number(p1[10]), p2Time = Number(p2[10]); const p1Laps = Number(p1[11]), p2Laps = Number(p2[11]); if (p1Laps === p2Laps) { const gapBehind = p2Time - p1Time; // s if (gapBehind > 0) { const blocks = Math.floor(gapBehind / 4); p1GapBonus = Math.min(blocks * dominancePerGap, dominanceMax); } } } const posScore = (finishingPos) => { if (finishingPos === 1) return 4; if (finishingPos === 2) return 2; if (finishingPos === 3) return 1; if (finishingPos > 13) return -10; if (finishingPos > 10) return -7; if (finishingPos > 8) return -2; return 0; }; const validRows = rows.filter(r => Number(r[7]) !== 1 && Number(r[5]) > 0 && Number(r[4]) > 0 && Number(r[4]) !== 99); const gridSize = validRows.length; const teamBonus = (teamRank, finishingPos) => { const factor = gridSize > 0 ? (gridSize / 20) : 1; const expectedPos = (2 * teamRank - 0.5) * factor; const delta = expectedPos - finishingPos; let bonus = delta * TEAM_WEIGHT; if (TEAM_BONUS_CAP > 0) { if (bonus > TEAM_BONUS_CAP) bonus = TEAM_BONUS_CAP; if (bonus < -TEAM_BONUS_CAP) bonus = -TEAM_BONUS_CAP; } return bonus; }; const rand = seededRandom(raceId); const RANDOM_INTENSITY = 0.8; const rowsScored = []; for (const row of rows) { const driverId = Number(row[2]); const teamId = Number(row[3]); const finishingPos = Number(row[4]); const startingPos = Number(row[5]); const dnf = Number(row[7]) === 1; if (dnf || startingPos <= 0 || finishingPos <= 0 || finishingPos === 99) continue; const gain = startingPos - finishingPos; const ps = posScore(finishingPos); const tr = Number(teamRankByTeamId.get(teamId)); const tb = teamBonus(tr, finishingPos); const dominanceBonus = (finishingPos === 1) ? p1GapBonus : 0; const poleBonus = (startingPos === 1) ? 1.0 : 0.0; const randomOffset = (rand() - 0.5) * RANDOM_INTENSITY; const scoreRaw = gain + ps + tb + dominanceBonus + randomOffset + poleBonus; const name = getNameByIdAndFormat(driverId); rowsScored.push({ driverId, name: name[0], scoreRaw, // <-- guardamos el bruto para depurar finishPos: finishingPos, startPos: startingPos, teamId, components: { gain, posScore: ps, teamBonus: tb, dominanceBonus, poleBonus, randomOffset } }); } // Orden: mayor scoreRaw rowsScored.sort((a, b) => (b.scoreRaw - a.scoreRaw) || (a.finishPos - b.finishPos) ); return rowsScored; } function softmaxToPercent(values, temperature = 1.0) { // estabilidad numérica const maxV = Math.max(...values); const exps = values.map(v => Math.exp((v - maxV) / temperature)); const sum = exps.reduce((a, b) => a + b, 0); return exps.map(x => (x / sum) * 100); } function softmaxToPercentBounded(values, opts = {}) { const { maxSharePct = 45, initialTemperature = 1.0, maxTemperature = 256, minTemperature = 0.001, iterations = 28 } = opts; if (!values || values.length === 0) return []; if (values.length === 1) return [100]; // Si el tope es matemáticamente imposible (p.ej. solo 2 candidatos), usamos el máximo factible. const feasibleCap = (values.length * maxSharePct >= 100) ? maxSharePct : (100 / values.length); const maxOf = (arr) => arr.reduce((m, v) => (v > m ? v : m), -Infinity); const sharesAt = (t) => softmaxToPercent(values, t); let lo = Math.max(minTemperature, Number(initialTemperature) || 1.0); let sharesLo = sharesAt(lo); if (maxOf(sharesLo) <= feasibleCap) return sharesLo; let hi = lo; let sharesHi = sharesLo; while (hi < maxTemperature) { hi *= 2; sharesHi = sharesAt(hi); if (maxOf(sharesHi) <= feasibleCap) break; } // Si aún no se cumple, devolvemos lo mejor que tenemos (distribución más "plana"). if (maxOf(sharesHi) > feasibleCap) return sharesHi; // Búsqueda binaria: al subir la temperatura, el máximo share baja (más uniforme). for (let i = 0; i < iterations; i++) { const mid = (lo + hi) / 2; const sharesMid = sharesAt(mid); if (maxOf(sharesMid) > feasibleCap) lo = mid; else { hi = mid; sharesHi = sharesMid; } } return sharesHi; } function roundPercentsToTargetSum(values, opts = {}) { const { decimals = 1, targetSum = 100, maxPerItem = Infinity } = opts; if (!values || values.length === 0) return []; const factor = Math.pow(10, decimals); const targetUnits = Math.round(targetSum * factor); const capUnits = Math.floor(maxPerItem * factor + 1e-9); const rawUnits = values.map(v => { const unit = Math.round((Number(v) || 0) * factor * 1e12) / 1e12; return unit; }); const floorUnits = rawUnits.map(u => Math.floor(u)); let remaining = targetUnits - floorUnits.reduce((a, b) => a + b, 0); const frac = rawUnits.map((u, i) => ({ i, frac: u - floorUnits[i] })); frac.sort((a, b) => b.frac - a.frac); // Reparte las décimas restantes priorizando las fracciones más grandes, respetando el tope cuando sea posible. let guard = 0; while (remaining > 0 && guard++ < (values.length * 5 + 1000)) { let progressed = false; for (const { i } of frac) { if (remaining <= 0) break; if (floorUnits[i] + 1 > capUnits) continue; floorUnits[i] += 1; remaining -= 1; progressed = true; } if (!progressed) break; } // Si el tope impidió sumar exactamente 100 (raro), prioriza sumar 100 antes que clavar el tope. if (remaining > 0) { for (const { i } of frac) { if (remaining <= 0) break; floorUnits[i] += 1; remaining -= 1; } } return floorUnits.map(u => u / factor); } export function upsertDoDRanking(season, raceId, leaderboard, topN = 3) { const top = leaderboard.slice(0, topN); for (let i = 0; i < top.length; i++) { const { driverId, scoreRaw, name, teamId } = top[i]; const rank = i + 1; queryDB(` INSERT INTO Custom_DriverOfTheDay_Ranking (Season, RaceID, Rank, DriverID, Name, TeamID, Score) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(Season, RaceID, Rank) DO UPDATE SET DriverID = excluded.DriverID, Name = excluded.Name, TeamID = excluded.TeamID, Score = excluded.Score `, [season, raceId, rank, driverId, name, teamId, Number(scoreRaw) || 0], 'run'); } } export function getDoDTopNForRace(season, raceId, topN = 3) { const rows = queryDB(` SELECT DriverID, Rank, Name, Score, TeamID FROM Custom_DriverOfTheDay_Ranking WHERE Season = ? AND RaceID = ? ORDER BY Rank ASC LIMIT ? `, [season, raceId, topN], 'allRows') || []; return rows.map(([driverId, rank, name, score, teamId]) => ({ driverId: Number(driverId), rank: Number(rank), name, score: Number(score), teamId: Number(teamId) })); } export function ensureCustomDoDRankingTable() { queryDB(` CREATE TABLE IF NOT EXISTS Custom_DriverOfTheDay_Ranking ( Season INTEGER NOT NULL, RaceID INTEGER NOT NULL, Rank INTEGER NOT NULL, -- 1,2,3... DriverID INTEGER NOT NULL, Name TEXT NOT NULL, Score REAL NOT NULL, TeamID INTEGER NOT NULL, PRIMARY KEY (Season, RaceID, Rank) ) `, [], 'run'); } export function fetchDriverOfTheDayCounts(season) { const rows = queryDB(` SELECT t.DriverID, ( SELECT w.Name FROM Custom_DriverOfTheDay_Ranking w JOIN Races r ON r.RaceID = w.RaceID WHERE w.Season = ? AND w.Rank = 1 AND w.DriverID = t.DriverID ORDER BY r.Day DESC, w.RaceID DESC LIMIT 1 ) AS Name, COALESCE(( SELECT w.TeamID FROM Custom_DriverOfTheDay_Ranking w JOIN Races r ON r.RaceID = w.RaceID WHERE w.Season = ? AND w.Rank = 1 AND w.DriverID = t.DriverID ORDER BY r.Day DESC, w.RaceID DESC LIMIT 1 ), -1) AS TeamID, COUNT(*) AS Count FROM Custom_DriverOfTheDay_Ranking t WHERE t.Season = ? AND t.Rank = 1 GROUP BY t.DriverID ORDER BY Count DESC `, [season, season, season], 'allRows') || []; return rows.map(r => ({ id: Number(r[0]), name: r[1], teamId: Number(r[2]), count: Number(r[3]) || 0 })); } export function fetchTeamMateQualiRaceHeadToHead(season) { const globals = getGlobals(); const teams = globals.isCreateATeam ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const lastRaceId = queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? AND State = 2 ORDER BY Day DESC, RaceID DESC LIMIT 1 `, [season], 'singleValue'); if (!lastRaceId) return []; const normalizePos = (pos) => { const n = Number(pos); if (n <= 0 || n === 99) return 999; return n; }; const pickTwoDriversForTeamAtLastRace = (teamId) => { const rows = queryDB(` SELECT DriverID, FinishingPos FROM Races_Results WHERE Season = ? AND RaceID = ? AND TeamID = ? ORDER BY CASE WHEN FinishingPos IS NULL OR FinishingPos <= 0 OR FinishingPos = 99 THEN 999 ELSE FinishingPos END ASC, DriverID ASC `, [season, lastRaceId, teamId], 'allRows') || []; const ids = []; for (const r of rows) { const id = Number(r[0]); if (!ids.includes(id)) ids.push(id); if (ids.length >= 2) break; } return ids; }; const getDriverName = (driverId) => { const row = queryDB(` SELECT FirstName, LastName, StaffID, 1 FROM Staff_BasicData WHERE StaffID = ? `, [driverId], 'singleRow'); const formatted = formatNamesSimple(row || ["", "", driverId, 1]); return formatted[0] || ""; }; const results = []; for (const teamId of teams) { const driverIds = pickTwoDriversForTeamAtLastRace(teamId); if (driverIds.length < 2) continue; const [driver1Id, driver2Id] = driverIds; const driver1Name = getDriverName(driver1Id); const driver2Name = getDriverName(driver2Id); const rows = queryDB(` SELECT RaceID, DriverID, FinishingPos, StartingPos FROM Races_Results WHERE Season = ? AND TeamID = ? AND DriverID IN (?, ?) ORDER BY RaceID `, [season, teamId, driver1Id, driver2Id], 'allRows') || []; const byRace = new Map(); for (const [raceId, driverId, finishingPos, startingPos] of rows) { const rid = Number(raceId); if (!byRace.has(rid)) byRace.set(rid, new Map()); byRace.get(rid).set(Number(driverId), { finishingPos, startingPos }); } let raceAhead1 = 0, raceAhead2 = 0; let qualiAhead1 = 0, qualiAhead2 = 0; for (const raceMap of byRace.values()) { const d1 = raceMap.get(driver1Id); const d2 = raceMap.get(driver2Id); if (!d1 || !d2) continue; const fin1 = normalizePos(d1.finishingPos); const fin2 = normalizePos(d2.finishingPos); if (fin1 < fin2) raceAhead1++; else if (fin2 < fin1) raceAhead2++; const st1 = normalizePos(d1.startingPos); const st2 = normalizePos(d2.startingPos); if (st1 < st2) qualiAhead1++; else if (st2 < st1) qualiAhead2++; } results.push({ teamId: Number(teamId), driver1Id, driver2Id, driver1Name, driver2Name, raceHeadToHead: `${raceAhead1}-${raceAhead2}`, qualiHeadToHead: `${qualiAhead1}-${qualiAhead2}`, raceAhead1, raceAhead2, qualiAhead1, qualiAhead2 }); } return results; } function seededRandom(seed) { // xmur3 + mulberry32 style let t = (seed + 0x6D2B79F5) | 0; return function () { t ^= t << 13; t ^= t >>> 17; t ^= t << 5; return ((t < 0 ? ~t + 1 : t) % 4294967296) / 4294967296; }; } function getNameByIdAndFormat(driverID) { const driverNameRow = queryDB(` SELECT FirstName, LastName, StaffID FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleRow'); const name = formatNamesSimple(driverNameRow); return name; } export function fetchOneDriverQualiResults(driver, year) { const driverID = Array.isArray(driver) ? driver[0] : driver; const season = Array.isArray(year) ? year[0] : year; const results = queryDB(` SELECT DriverID, TeamID, StartingPos, Points FROM Races_Results WHERE Season = ? AND DriverID = ? `, [season, driverID], 'allRows') || []; if (results.length > 0) { const teamID = results[0][1]; const driverNameRow = queryDB(` SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleRow'); return formatSeasonResults( results, driverNameRow, teamID, driver, year, [], true ); } return null; } export function fetchEventsDoneFrom(year) { const daySeasonRow = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!daySeasonRow) { return []; } const [currentDay, currentSeason] = daySeasonRow; const seasonIdsRows = queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? AND Day < ? `, [year, currentDay], 'allRows') || []; const eventsIds = seasonIdsRows.map(row => row[0]); return eventsIds; } export function fetchEventsDoneBefore(year, day) { const daySeasonRow = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); if (!daySeasonRow) { return []; } const seasonIdsRows = queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? AND Day < ? `, [year, day], 'allRows') || []; const eventsIds = seasonIdsRows.map(row => row[0]); return eventsIds; } export function fetchEventsFrom(year, formula = 1) { const seasonEventsRows = queryDB(` SELECT r.RaceID, r.TrackID, r.WeekendType, r.State, t.isF2Race, t.IsF3Race FROM Races r LEFT JOIN Races_Tracks t ON r.TrackID = t.TrackID WHERE r.SeasonID = ? ORDER BY r.RaceID `, [year], 'allRows') || []; if (Number(formula) === 2) { return seasonEventsRows.filter(row => Number(row[4]) === 1); } if (Number(formula) === 3) { return seasonEventsRows.filter(row => Number(row[5]) === 1); } return seasonEventsRows; } export function fetchLastCompletedRaceId(year, formula = 1) { const filterSql = Number(formula) === 2 ? `AND t.isF2Race = 1` : (Number(formula) === 3 ? `AND t.IsF3Race = 1` : ``); return queryDB(` SELECT r.RaceID FROM Races r LEFT JOIN Races_Tracks t ON r.TrackID = t.TrackID WHERE r.SeasonID = ? AND r.State = 2 ${filterSql} ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 `, [year], 'singleValue'); } function fetchPracticeResultsRows(raceId, practiceSession = 1) { const raceIdNum = Number(raceId); const sessionNum = Number(practiceSession); const rows = queryDB(` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.BestLapTime, res.LapCount FROM Staff_BasicData bas JOIN Races_PracticeResults res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? AND res.RaceFormula = 1 AND res.PracticeSession = ? ORDER BY CASE WHEN res.BestLapTime IS NULL OR res.BestLapTime <= 0 THEN 1 ELSE 0 END, res.BestLapTime ASC `, [raceIdNum, sessionNum], 'allRows') || []; return { rows, error: null }; } function fetchRaceResultsRows(raceId) { return queryDB(` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.FinishingPos, res.StartingPos, res.Points, res.DNF, res.SafetyCarDeployments, res.VirtualSafetyCarDeployments, res.Time, res.Laps, res.FastestLap FROM Staff_BasicData bas JOIN Races_Results res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? ORDER BY res.FinishingPos `, [raceId], 'allRows') || []; } function fetchSprintResultsRows(raceId) { return queryDB(` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.FinishingPos, res.ChampionshipPoints, res.DNF, res.RaceTime, res.LapCount, res.FastestLap FROM Staff_BasicData bas JOIN Races_SprintResults res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? AND res.RaceFormula = 1 ORDER BY res.FinishingPos `, [raceId], 'allRows') || []; } function fetchQualifyingResultsRows(raceId, sprintShootout = 0) { return queryDB(` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.FinishingPos, ( SELECT q1.FastestLap FROM Races_QualifyingResults q1 WHERE q1.RaceID = res.RaceID AND q1.DriverID = res.DriverID AND q1.RaceFormula = 1 AND q1.SprintShootout = ? AND q1.QualifyingStage = 1 ) AS Q1FastestLap, ( SELECT q2.FastestLap FROM Races_QualifyingResults q2 WHERE q2.RaceID = res.RaceID AND q2.DriverID = res.DriverID AND q2.RaceFormula = 1 AND q2.SprintShootout = ? AND q2.QualifyingStage = 2 ) AS Q2FastestLap, ( SELECT q3.FastestLap FROM Races_QualifyingResults q3 WHERE q3.RaceID = res.RaceID AND q3.DriverID = res.DriverID AND q3.RaceFormula = 1 AND q3.SprintShootout = ? AND q3.QualifyingStage = 3 ) AS Q3FastestLap, res.ChampionshipPoints, res.GridPenalty AS GridPenalty, res.LapCount FROM Staff_BasicData bas JOIN Races_QualifyingResults res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? AND res.RaceFormula = 1 AND res.SprintShootout = ? AND res.QualifyingStage = (SELECT MAX(res2.QualifyingStage) FROM Races_QualifyingResults res2 WHERE res2.RaceID = ? AND res2.DriverID = res.DriverID AND res2.RaceFormula = 1 AND res2.SprintShootout = ?) ORDER BY res.FinishingPos; `, [sprintShootout, sprintShootout, sprintShootout, raceId, sprintShootout, raceId, sprintShootout], 'allRows') || []; } function buildQualifyingGridPositionMap(rows) { const orderedRows = (rows || []) .map((row) => ({ driverId: Number(row?.[2]), pos: Number(row?.[4]), gridPenalty: Number(row?.[9]) })) .filter((row) => row.driverId > 0 && row.pos > 0) .sort((a, b) => a.pos - b.pos); const slots = new Array(orderedRows.length).fill(null); const penalizedRows = orderedRows .filter((row) => row.gridPenalty > 0) .sort((a, b) => b.pos - a.pos); penalizedRows.forEach((row) => { const targetPos = Math.min(orderedRows.length, row.pos + row.gridPenalty); for (let slotIdx = targetPos - 1; slotIdx >= 0; slotIdx--) { if (slots[slotIdx] == null) { slots[slotIdx] = row; return; } } }); let nextFreeSlot = 0; orderedRows .filter((row) => row.gridPenalty <= 0) .forEach((row) => { while (slots[nextFreeSlot] != null) nextFreeSlot++; if (nextFreeSlot < slots.length) { slots[nextFreeSlot] = row; nextFreeSlot++; } }); const positionByDriverId = new Map(); slots.forEach((row, idx) => { if (!row) return; positionByDriverId.set(row.driverId, idx + 1); }); return positionByDriverId; } export function fetchSessionResults(raceId, sessionKey, gameYear = "24") { const raceIdNum = Number(raceId); const key = String(sessionKey || "").toLowerCase(); const gy = String(gameYear || "").trim(); const raceMeta = queryDB(`SELECT TrackID, WeekendType FROM Races WHERE RaceID = ?`, [raceIdNum], 'singleRow') || []; const trackId = Number(raceMeta[0]); const weekendType = Number(raceMeta[1]); const meta = { raceId: raceIdNum, trackId, weekendType, sessionKey: key }; const driverRaceNumberCache = new Map(); const getDriverRaceNumber = (driverId) => { const id = Number(driverId); if (id <= 0) return 0; if (!driverRaceNumberCache.has(id)) { const [raceNumber] = fetchDriverNumberDetails(id); driverRaceNumberCache.set(id, Number(raceNumber) || 0); } return driverRaceNumberCache.get(id) ?? 0; }; if (key === "quali" || key === "sprintquali") { const pointsInfo = fetchPointsRegulations(); meta.hasPolePositionPoints = Number(pointsInfo?.poleBonusPoint) === 1 ? 1 : 0; } if (key === "race") { try { const seasonId = queryDB(`SELECT SeasonID FROM Races WHERE RaceID = ?`, [raceIdNum], 'singleValue'); if (seasonId != null) { ensureCustomDoDRankingTable(); let dotdDriverId = queryDB( `SELECT DriverID FROM Custom_DriverOfTheDay_Ranking WHERE Season = ? AND RaceID = ? AND Rank = 1`, [seasonId, raceIdNum], 'singleValue' ); if (dotdDriverId == null) { const teamRows = queryDB( `SELECT TeamID, Position FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = 1`, [seasonId], 'allRows' ) || []; const teamRankByTeamId = new Map(); for (const r of teamRows) teamRankByTeamId.set(Number(r?.[0]), Number(r?.[1])); const rawRaceRows = queryDB( `SELECT DriverID, TeamID, StartingPos, FinishingPos, DNF, Time, Laps FROM Races_Results WHERE RaceID = ?`, [raceIdNum], 'allRows' ) || []; const raceRows = rawRaceRows.map((r) => ([ null, null, Number(r?.[0]), Number(r?.[1]), Number(r?.[3]), Number(r?.[2]), null, Number(r?.[4]), null, null, Number(r?.[5]), Number(r?.[6]) ])).sort((a, b) => Number(a[4]) - Number(b[4])); const leaderboard = computeDriverOfTheDayLeaderboardFromRows(raceRows, raceIdNum, { teamRankByTeamId }); if (leaderboard?.length) { upsertDoDRanking(Number(seasonId), raceIdNum, leaderboard, 3); dotdDriverId = Number(leaderboard[0].driverId); } } if (dotdDriverId != null) meta.dotdDriverId = Number(dotdDriverId); } } catch (e) { // ignore DoD lookup failures } const rows = fetchRaceResultsRows(raceIdNum); const results = rows.map((row) => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { pos: row[4], grid: row[5], points: row[6], dnf: row[7], safetyCar: row[8], virtualSafetyCar: row[9], time: row[10], laps: row[11], driverId, teamId, raceNumber: getDriverRaceNumber(driverId), name: nameFormatted, fastestLap: row[12], nationality: gy ? fetchNationality(driverId, gy) : "", }; }); return { meta, results }; } if (key === "sprintrace") { const rows = fetchSprintResultsRows(raceIdNum); const results = rows.map((row) => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { pos: row[4], points: row[5], dnf: row[6], time: row[7], laps: row[8], driverId, teamId, raceNumber: getDriverRaceNumber(driverId), name: nameFormatted, fastestLap: row[9], nationality: gy ? fetchNationality(driverId, gy) : "", }; }); return { meta, results }; } if (key === "quali" || key === "sprintquali") { const shootout = key === "sprintquali" ? 1 : 0; const rows = fetchQualifyingResultsRows(raceIdNum, shootout); const qualifyingGridPositions = buildQualifyingGridPositionMap(rows); const results = rows.map((row) => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); const qualifyingPos = Number(row[4]); const gridPenalty = Number(row[9]); return { pos: qualifyingPos, q1FastestLap: row[5], q2FastestLap: row[6], q3FastestLap: row[7], driverId, teamId, raceNumber: getDriverRaceNumber(driverId), name: nameFormatted, points: row[8], gridPenalty, gridPosition: Number(qualifyingGridPositions.get(driverId) ?? 0), laps: Number(row[10]) || 0, nationality: gy ? fetchNationality(driverId, gy) : "", }; }); return { meta, results }; } if (key === "fp" || key === "fp1" || key === "fp2" || key === "fp3") { const practiceSession = key === "fp2" ? 2 : (key === "fp3" ? 3 : 1); const { rows, error } = fetchPracticeResultsRows(raceIdNum, practiceSession); if (error) return { meta: { ...meta, error }, results: [] }; const results = rows.map((row, idx) => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { pos: idx + 1, fastestLap: row[4], laps: row[5], driverId, teamId, raceNumber: getDriverRaceNumber(driverId), name: nameFormatted, nationality: gy ? fetchNationality(driverId, gy) : "", }; }); return { meta, results }; } return { meta: { ...meta, error: "Unknown session key" }, results: [] }; } function formatDriverName(driverName) { let nombre = "", apellido = ""; const firstName = driverName ? driverName[0] : ""; const lastName = driverName ? driverName[1] : ""; if (!firstName.includes("STRING_LITERAL")) { const m = firstName.match(/StaffName_Forename_(Male|Female)_(\w+)/); nombre = m ? removeNumber(m[2]) : ""; } else { const m = firstName.match(/\|([^|]+)\|/); nombre = m ? m[1] : ""; } if (!lastName.includes("STRING_LITERAL")) { const m = lastName.match(/StaffName_Surname_(\w+)/); apellido = m ? removeNumber(m[1]) : ""; } else { const m = lastName.match(/\|([^|]+)\|/); apellido = m ? m[1] : ""; } return `${nombre} ${apellido}`.trim(); } function formatSeasonResultsF2F3( results, driverName, teamID, driver, year, sprints, formula ) { const driverID = Array.isArray(driver) ? driver[0] : driver; const season = Array.isArray(year) ? year[0] : year; const nameFormatted = formatDriverName(driverName); const raceObjects = []; const byRace = new Map(); results.forEach((row) => { const raceID = row[0]; const teamId = row[1] ?? teamID; const finishingPos = row[2]; const points = row[3]; const dnf = Number(row[4]) === 1; const base = { raceId: raceID, finishingPos: finishingPos ?? 99, points: points ?? 0, dnf: dnf, fastestLap: false, qualifyingPos: 99, qualifyingPoints: 0, gapToWinner: null, gapToPole: null, startingPos: 99, gapAhead: null, gapBehind: null, sprintPoints: 0, sprintPos: null, sprintQualiPos: null, teamId: teamId, driverOfTheDay: false }; if (base.dnf) { base.finishingPos = -1; base.points = -1; } const qualiRow = queryDB(` SELECT FinishingPos, ChampionshipPoints FROM Races_QualifyingResults WHERE RaceFormula = ? AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = 1 `, [formula, raceID, season, driverID], "singleRow") || []; const qualiPos = qualiRow[0] ?? 99; base.qualifyingPos = qualiPos; base.qualifyingPoints = qualiRow[1] ?? 0; base.startingPos = qualiPos; const invertLimit = Number(formula) === 2 ? 10 : (Number(formula) === 3 ? 12 : 0); if (invertLimit > 0) { const qPos = Number(qualiPos); base.sprintQualiPos = (qPos > 0 && qPos <= invertLimit) ? (invertLimit + 1 - qPos) : qPos; } else { base.sprintQualiPos = qualiPos; } const fastestLapDriver = queryDB(` SELECT DriverID FROM Races_FeatureRaceResults WHERE FastestLap > 0 AND RaceID = ? AND SeasonID = ? AND RaceFormula = ? ORDER BY FastestLap LIMIT 1 `, [raceID, season, formula], "singleValue"); base.fastestLap = parseInt(fastestLapDriver) === parseInt(driverID); raceObjects.push(base); byRace.set(raceID, base); }); if (Array.isArray(sprints)) { for (const sprintRow of sprints) { const [sprintRaceID, sprintPos, sprintPoints] = sprintRow; const obj = byRace.get(sprintRaceID); if (obj) { obj.sprintPoints = sprintPoints ?? 0; obj.sprintPos = sprintPos ?? null; } } } const latestTeamId = raceObjects.length ? raceObjects[raceObjects.length - 1].teamId : teamID; const standingsRow = queryDB(` SELECT Position, LastPositionChange FROM Races_Driverstandings WHERE RaceFormula = ? AND SeasonID = ? AND DriverID = ? `, [formula, season, driverID], "singleRow") || [0, 0]; const championshipPosition = Number(standingsRow[0]) || 0; const lastPositionChange = Number(standingsRow[1]) || 0; return { driverName: nameFormatted, latestTeamId, driverId: driverID[0] || driverID, championshipPosition, lastPositionChange, races: raceObjects }; } export function formatSeasonResults( results, driverName, teamID, driver, year, sprints, isCurrentYear = true, formula = 1 ) { if (Number(formula) !== 1) { return formatSeasonResultsF2F3(results, driverName, teamID, driver, year, sprints, formula); } const driverID = Array.isArray(driver) ? driver[0] : driver; const season = Array.isArray(year) ? year[0] : year; const toSeconds = (t) => { if (t == null) return null; if (typeof t === "number") return t; const s = String(t).trim(); if (s === "" || s.toUpperCase() === "NR") return null; const numberRe = /^[+-]?\d+(\.\d+)?$/; const clean = s.startsWith("(") && s.endsWith(")") ? s.slice(1, -1) : s; if (clean.includes(":")) { const [mm, rest] = clean.split(":"); const mmStr = String(mm).trim(); const secsStr = String(rest).trim(); if (!/^\d+$/.test(mmStr)) return null; if (!numberRe.test(secsStr)) return null; return parseInt(mmStr, 10) * 60 + parseFloat(secsStr); } const nStr = String(clean).trim(); if (!numberRe.test(nStr)) return null; return parseFloat(nStr); }; const formatGap = (delta) => { if (delta == null) return null; return `+${delta.toFixed(3)}`; }; const nameFormatted = formatDriverName(driverName); // ---- carreras del piloto ---- const racesParticipated = queryDB(` SELECT RaceID FROM Races_Results WHERE DriverID = ? AND Season = ? ORDER BY RaceID `, [driverID, season], "allRows") || []; const raceObjects = []; const myBasicsRows = queryDB(` SELECT RaceID, TeamID, FinishingPos, Points, StartingPos, DNF FROM Races_Results WHERE Season = ? AND DriverID = ? ORDER BY RaceID `, [season, driverID], "allRows") || []; const myBasicsByRace = new Map(); myBasicsRows.forEach((r) => { myBasicsByRace.set(Number(r[0]), { teamId: r[1] ?? teamID, finishingPos: r[2], points: r[3], startingPos: r[4], dnf: Number(r[5]) === 1 }); }); for (let i = 0; i < racesParticipated.length; i++) { const raceID = racesParticipated[i][0]; // Traemos resultados completos de la carrera para calcular gaps/startingPos en una sola query const raceResults = queryDB(` SELECT DriverID, FinishingPos, Points, Time, StartingPos, DNF FROM Races_Results WHERE Season = ? AND RaceID = ? `, [season, raceID], "allRows") || []; // info específica del piloto const myRow = raceResults.find(r => Number(r[0]) === Number(driverID)); const myBasic = myBasicsByRace.get(Number(raceID)) || null; const myDNF = myBasic ? (myBasic.dnf ? 1 : 0) : (myRow ? (Number(myRow[5]) === 1) : 0); const myStartingPos = myBasic ? (myBasic.startingPos ?? 99) : (myRow ? (myRow[4] ?? 99) : 99); // vuelta rápida (tu lógica) const driverWithFastestLap = queryDB(` SELECT DriverID FROM Races_Results WHERE FastestLap > 0 AND RaceID = ? AND Season = ? ORDER BY FastestLap LIMIT 1 `, [raceID, season], "singleValue"); // objeto base const base = { raceId: raceID, finishingPos: myBasic ? (myBasic.finishingPos ?? 99) : (myRow ? (myRow[1] ?? 99) : 99), points: myDNF ? -1 : (myBasic ? (myBasic.points ?? 0) : (myRow ? (myRow[2] ?? 0) : 0)), dnf: myDNF, fastestLap: parseInt(driverWithFastestLap) === parseInt(driverID), qualifyingPos: 99, qualifyingPoints: 0, gapToWinner: null, gapToPole: null, // NUEVOS CAMPOS: startingPos: myStartingPos, gapAhead: null, gapBehind: null, // sprint/equipo sprintPoints: 0, sprintPos: null, teamId: 0, driverOfTheDay: false }; if (base.dnf) { base.finishingPos = -1; base.points = -1; } // Quali / parrilla (como antes) let QRes; let QPts = 0; if (isCurrentYear) { const QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, season, driverID], "singleValue") || 0; const qRow = queryDB(` SELECT FinishingPos, ChampionshipPoints FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, season, driverID, QStage], "singleRow") || []; QRes = qRow[0] ?? 99; QPts = qRow[1] ?? 0; } else { QRes = queryDB(` SELECT StartingPos FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceID, driverID], "singleValue") || 99; const QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, season, driverID], "singleValue") || 0; QPts = queryDB(` SELECT ChampionshipPoints FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, season, driverID, QStage], "singleValue") || 0; } base.qualifyingPos = QRes; base.qualifyingPoints = QPts ?? 0; // Gaps generales (tus funciones existentes) base.gapToWinner = calculateTimeDifference(driverID, raceID); base.gapToPole = calculateTimeToPole(driverID, raceID); // --- NUEVO: calcular gapAhead / gapBehind con todos los clasificados --- if (!base.dnf && raceResults.length > 0) { // clasificados con tiempo interpretable const classified = raceResults .filter(r => Number(r[1]) > 0) // FinishingPos > 0 .sort((a, b) => Number(a[1]) - Number(b[1])); // por posición const idx = classified.findIndex(r => Number(r[0]) === Number(driverID)); if (idx !== -1) { const myTime = toSeconds(classified[idx][3]); // Time // delante if (idx > 0) { const aheadTime = toSeconds(classified[idx - 1][3]); if (myTime != null && aheadTime != null) { base.gapAhead = formatGap(myTime - aheadTime); } } // detrás if (idx < classified.length - 1) { const behindTime = toSeconds(classified[idx + 1][3]); if (myTime != null && behindTime != null) { base.gapBehind = formatGap(behindTime - myTime); } } } } // gaps listos // equipo por carrera const teamInRace = queryDB(` SELECT TeamID FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceID, driverID], "singleValue") || 0; base.teamId = teamInRace; raceObjects.push(base); } // Sprints if (Array.isArray(sprints)) { const byRace = new Map(raceObjects.map(o => [o.raceId, o])); for (const sprintRow of sprints) { const [sprintRaceID, sprintPos, sprintPoints] = sprintRow; const obj = byRace.get(sprintRaceID); if (obj) { obj.sprintPoints = sprintPoints ?? 0; obj.sprintPos = sprintPos ?? null; } } } // último equipo / posición campeonato const latestTeamId = raceObjects.length ? raceObjects[raceObjects.length - 1].teamId : teamID; const standingsRow = queryDB(` SELECT Position, LastPositionChange FROM Races_Driverstandings WHERE RaceFormula = ? AND SeasonID = ? AND DriverID = ? `, [formula, season, driverID], "singleRow") || [0, 0]; const championshipPosition = Number(standingsRow[0]) || 0; const lastPositionChange = Number(standingsRow[1]) || 0; const payload = { driverName: nameFormatted, latestTeamId, driverId: driverID[0] || driverID, championshipPosition, lastPositionChange, races: raceObjects }; return payload; } export function calculateTimeToPole(driverID, raceID) { const QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND DriverID = ? `, [raceID, driverID], 'singleValue') || 0; const poleTime = queryDB(` SELECT MIN(FastestLap) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND QualifyingStage = 3 AND FastestLap IS NOT 0 `, [raceID], 'singleValue') || 9999; const driverTime = queryDB(` SELECT FastestLap FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND QualifyingStage = ? AND DriverID = ? `, [raceID, QStage, driverID], 'singleValue') || 9999; if (driverTime < poleTime) { return "NR"; } else { const difference = Number((driverTime - poleTime).toFixed(2)); return `+${difference}s`; } } export function calculateTimeDifference(driverID, raceID) { const totalLaps = queryDB(` SELECT MAX(Laps) FROM Races_Results WHERE RaceID = ? `, [raceID], 'singleValue') || 0; const driverLaps = queryDB(` SELECT Laps FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceID, driverID], 'singleValue') || 0; if (driverLaps < totalLaps) { return `+${totalLaps - driverLaps} L`; } else { const winnerID = queryDB(` SELECT DriverID FROM Races_Results WHERE RaceID = ? AND FinishingPos = 1 `, [raceID], 'singleValue'); const winnerTime = queryDB(` SELECT Time FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceID, winnerID], 'singleValue') || 0; const driverTime = queryDB(` SELECT Time FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceID, driverID], 'singleValue') || 0; const timeDiff = Number((driverTime - winnerTime).toFixed(1)); return `+${timeDiff}s`; } } export function fetchDriverNumbers() { const numbers = queryDB(`SELECT DISTINCT Number FROM Staff_DriverNumbers dn `, [], 'allRows'); return numbers.map(n => n[0]); } export function fetchDriverContracts(id) { // Obtener el contrato actual const currentContract = queryDB(` SELECT Salary, EndSeason, StartingBonus, RaceBonus, RaceBonusTargetPos, TeamID FROM Staff_Contracts WHERE ContractType = 0 AND StaffID = ? AND (TeamID BETWEEN 1 AND 10 OR TeamID = 32) `, [id], 'singleRow'); //teamID between 1 and 1 10 (10 included) and alsoc na be 32 // Obtener el contrato futuro const futureContract = queryDB(` SELECT Salary, EndSeason, StartingBonus, RaceBonus, RaceBonusTargetPos, PosInTeam, TeamID FROM Staff_Contracts WHERE ContractType = 3 AND StaffID = ? `, [id], 'singleRow'); // Obtener el día y la temporada actual const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); const juniorFormulasContract = queryDB(` SELECT Salary, EndSeason, StartingBonus, RaceBonus, RaceBonusTargetPos, PosInTeam, TeamID FROM Staff_Contracts WHERE ContractType = 0 AND StaffID = ? AND (TeamID > 10 AND TeamID <> 32) `, [id], 'singleRow'); let isDriver = queryDB(` SELECT COUNT(*) FROM Staff_DriverData WHERE StaffID = ? `, [id], 'singleValue'); //isDriver has to be true or false isDriver = isDriver > 0 ? true : false; // Retornar los resultados return [currentContract, futureContract, juniorFormulasContract, isDriver, daySeason ? daySeason[1] : null]; } function formatStaffNameFromLocKeys(firstNameLocKey, lastNameLocKey) { let firstName = ""; let lastName = ""; if (typeof firstNameLocKey === "string") { if (!firstNameLocKey.includes("STRING_LITERAL")) { const m = firstNameLocKey.match(/StaffName_Forename_(?:Male|Female)_(\w+)/); firstName = m ? removeNumber(m[1]) : ""; } else { const m = firstNameLocKey.match(/\|([^|]+)\|/); firstName = m ? m[1] : ""; } } if (typeof lastNameLocKey === "string") { if (!lastNameLocKey.includes("STRING_LITERAL")) { const m = lastNameLocKey.match(/StaffName_Surname_(\w+)/); lastName = m ? removeNumber(m[1]) : ""; } else { const m = lastNameLocKey.match(/\|([^|]+)\|/); lastName = m ? m[1] : ""; } } return `${firstName} ${lastName}`.trim(); } export function fetchJuniorTeamDriverNames(teamId) { const maxCars = (teamId >= 11 && teamId <= 21) ? 2 : (teamId >= 22 && teamId <= 31) ? 3 : 0; if (!maxCars) return []; const rows = queryDB(` SELECT bas.FirstName, bas.LastName, con.PosInTeam FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.ContractType = 0 AND con.TeamID = ? ORDER BY con.PosInTeam, bas.LastName, bas.FirstName `, [teamId], 'allRows') || []; const byPos = new Map(); rows.forEach(([firstName, lastName, posInTeam]) => { const pos = Number(posInTeam); if (pos < 1 || pos > maxCars) return; if (byPos.has(pos)) return; const name = formatStaffNameFromLocKeys(firstName, lastName); byPos.set(pos, name || "Free driver"); }); const result = []; for (let pos = 1; pos <= maxCars; pos++) { result.push({ name: byPos.get(pos) || "Free driver", posInTeam: pos }); } return result; } export function checkCustomTables(year) { let createdEnginesList = false; let createdEnginesStats = false; let createdEnginesAllocations = false; let createdCustomSaveConfig = false; let createdEngineRegulationState = false; const tablesToCheck = [ { name: 'Custom_Engines_List', createSQL: ` CREATE TABLE Custom_Engines_List ( engineId INTEGER PRIMARY KEY, name TEXT ) ` }, { name: 'Custom_Engines_Stats', createSQL: ` CREATE TABLE Custom_Engines_Stats ( engineId INTEGER, designId INTEGER, partStat INTEGER, unitValue REAL, Value REAL, PRIMARY KEY (engineId, designId, partStat) ) ` }, { name: 'Custom_Save_Config', createSQL: ` CREATE TABLE Custom_Save_Config ( key TEXT PRIMARY KEY, value TEXT ) ` }, { name: 'Custom_Engine_Allocations', createSQL: ` CREATE TABLE Custom_Engine_Allocations ( teamId INTEGER, engineId INTEGER ) ` }, { name: 'Custom_Engine_Regulation_State', createSQL: ` CREATE TABLE IF NOT EXISTS Custom_Engine_Regulation_State ( id INTEGER PRIMARY KEY CHECK (id = 1), lastSeasonApplied INTEGER ); ` }, { name: 'Custom_Engine_Progression', createSQL: ` CREATE TABLE Custom_Engine_Progression ( SeasonID INTEGER NOT NULL, RaceID INTEGER NOT NULL, EngineID INTEGER NOT NULL, Power REAL NOT NULL, Source TEXT NULL, PRIMARY KEY (SeasonID, RaceID, EngineID) ) ` } ]; tablesToCheck.forEach((table) => { const tableExists = queryDB(` SELECT name FROM sqlite_master WHERE type='table' AND name=? `, [table.name], 'singleValue'); if (!tableExists) { queryDB(table.createSQL, [], 'run'); if (table.name === 'Custom_Engines_List') { createdEnginesList = true; } else if (table.name === 'Custom_Engines_Stats') { createdEnginesStats = true; } else if (table.name === 'Custom_Engine_Allocations') { createdEnginesAllocations = true; } else if (table.name === 'Custom_Save_Config') { createdCustomSaveConfig = true; } else if (table.name === 'Custom_Engine_Regulation_State') { createdEngineRegulationState = true; } } }); fixCustomEnginesStatsTable(); insertDefualtEnginesData(createdEnginesList, createdEnginesStats, createdEnginesAllocations, createdCustomSaveConfig, createdEngineRegulationState, year); createEngineMigrationTrigger(); ensureCustomEngineProgressionTable(); } export function fixCustomEnginesStatsTable() { // Verificar si la tabla tiene la PRIMARY KEY const hasPrimaryKey = queryDB(` PRAGMA table_info(Custom_Engines_Stats); `, [], 'allRows'); // PRAGMA returns rows let primaryKeyExists = hasPrimaryKey.some( (column) => column[5] > 0 // Column index 5 is pk ); if (!primaryKeyExists) { queryDB(` CREATE TABLE Custom_Engines_Stats_TEMP ( engineId INTEGER, designId INTEGER, partStat INTEGER, unitValue REAL, Value REAL, PRIMARY KEY (engineId, designId, partStat) ); `, [], 'run'); queryDB(` INSERT INTO Custom_Engines_Stats_TEMP (engineId, designId, partStat, unitValue, Value) SELECT engineId, designId, partStat, unitValue, Value FROM Custom_Engines_Stats WHERE rowid IN ( SELECT MAX(rowid) FROM Custom_Engines_Stats GROUP BY engineId, designId, partStat ); `, [], 'run'); queryDB(`DROP TABLE Custom_Engines_Stats;`, [], 'run'); queryDB(`ALTER TABLE Custom_Engines_Stats_TEMP RENAME TO Custom_Engines_Stats;`, [], 'run'); } } export function wipeTableAndRefill(tableName, data){ queryDB(`DELETE FROM ${tableName};`, [], 'run'); data.forEach(row => { const placeholders = Object.keys(row).map(() => '?').join(', '); const sql = `INSERT INTO ${tableName} (${Object.keys(row).join(', ')}) VALUES (${placeholders});`; queryDB(sql, Object.values(row), 'run'); }); } export function insertDefualtEnginesData(list, stats, allocations, customSave, engineRegulationState, year) { const engines = [ { id: 1, name: 'Ferrari', stats: [ { partStat: 6, value: 500, unitValue: 75, designId: 1 }, { partStat: 10, value: 750, unitValue: 95, designId: 1 }, { partStat: 11, value: 250, unitValue: 80, designId: 1 }, { partStat: 12, value: 500, unitValue: 77.5, designId: 1 }, { partStat: 14, value: 400, unitValue: 68, designId: 1 }, { partStat: 15, value: 350, unitValue: 57, designId: 2 }, { partStat: 15, value: 0, unitValue: 50, designId: 3 } ] }, { id: 4, name: 'Red Bull', stats: [ { partStat: 6, value: 300, unitValue: 65, designId: 4 }, { partStat: 10, value: 1000, unitValue: 100, designId: 4 }, { partStat: 11, value: 0, unitValue: 85, designId: 4 }, { partStat: 12, value: 0, unitValue: 70, designId: 4 }, { partStat: 14, value: 0, unitValue: 60, designId: 4 }, { partStat: 15, value: 0, unitValue: 50, designId: 5 }, { partStat: 15, value: 600, unitValue: 62, designId: 6 } ] }, { id: 7, name: 'Mercedes', stats: [ { partStat: 6, value: 0, unitValue: 50, designId: 7 }, { partStat: 10, value: 500, unitValue: 90, designId: 7 }, { partStat: 11, value: 1000, unitValue: 65, designId: 7 }, { partStat: 12, value: 850, unitValue: 82.75, designId: 7 }, { partStat: 14, value: 1000, unitValue: 80, designId: 7 }, { partStat: 15, value: 1000, unitValue: 70, designId: 8 }, { partStat: 15, value: 1000, unitValue: 70, designId: 9 } ] }, { id: 10, name: 'Renault', stats: [ { partStat: 6, value: 1000, unitValue: 100, designId: 10 }, { partStat: 10, value: 0, unitValue: 80, designId: 10 }, { partStat: 11, value: 500, unitValue: 75, designId: 10 }, { partStat: 12, value: 1000, unitValue: 85, designId: 10 }, { partStat: 14, value: 650, unitValue: 73, designId: 10 }, { partStat: 15, value: 500, unitValue: 75, designId: 11 }, { partStat: 15, value: 1000, unitValue: 100, designId: 12 } ] } ]; const teams = { alphatauri: { 23: "alphatauri", 24: "visarb" }, alfa: { 23: "alfa", 24: "stake" }, alpine: { 23: "alpine", 24: "alpine" } } if (customSave) { for (let key in teams) { const newTeam = teams[key][year]; queryDB(`INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES (?, ?)`, [key, newTeam], 'run'); } queryDB( `INSERT OR IGNORE INTO Custom_Save_Config (key, value) VALUES ('turningPointsFrequencyPreset', ?)`, [String(defaultTurningPointsFrequencyPreset)], 'run' ); } if (list && stats) { engines.forEach(engine => { queryDB(` INSERT OR REPLACE INTO Custom_Engines_List (engineId, Name) VALUES (?, ?) `, [engine.id, engine.name], 'run'); engine.stats.forEach(stat => { queryDB(` INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?) `, [engine.id, stat.designId, stat.partStat, stat.value, stat.unitValue], 'run'); }); }); } if (allocations) { const maxYear = queryDB(`SELECT MAX(SeasonID) FROM Parts_TeamHistory`, [], 'singleValue'); const actualEngineAllocations = queryDB(` SELECT th.TeamID, em.EngineDesignID FROM Parts_TeamHistory th JOIN Parts_Enum_EngineManufacturers em ON th.EngineManufacturer = em.Value WHERE SeasonID = ?`, [maxYear], 'allRows'); actualEngineAllocations.forEach(engine => { queryDB(` INSERT OR REPLACE INTO Custom_Engine_Allocations (teamId, engineId) VALUES (?, ?) `, [engine[0], engine[1]], 'run'); }); } if (engineRegulationState) { queryDB(` INSERT OR IGNORE INTO Custom_Engine_Regulation_State (id, lastSeasonApplied) VALUES (1, -1); `, [], 'run'); } } export function updateCustomEngines(engineData) { for (let engineId in engineData) { const nameCapitalized = engineData[engineId].name.charAt(0).toUpperCase() + engineData[engineId].name.slice(1); queryDB(`INSERT OR REPLACE INTO Custom_Engines_List (engineId, Name) VALUES (?, ?)`, [engineId, nameCapitalized], 'run'); for (let stat in engineData[engineId].stats) { const untiValue = engineData[engineId].stats[stat]; const value = engine_unitValueToValue[stat](untiValue); if (parseInt(stat) !== 18 && parseInt(stat) !== 19) { queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, engineId, stat, value, untiValue], 'run'); } else if (parseInt(stat) === 18) { let designId = parseInt(engineId) + 1; queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, designId, 15, value, untiValue], 'run'); } else if (parseInt(stat) === 19) { let designId = parseInt(engineId) + 2; queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, designId, 15, value, untiValue], 'run'); } } updateTeamsSuppliedByEngine(engineId, engineData[engineId].stats); } } export function editEngines(engineData) { for (let engineId in engineData) { for (let stat in engineData[engineId]) { const untiValue = engineData[engineId][stat]; const value = engine_unitValueToValue[stat](untiValue); if (parseInt(stat) !== 18 && parseInt(stat) !== 19) { queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, engineId, stat, value, untiValue], 'run'); } else if (parseInt(stat) === 18) { let designId = parseInt(engineId) + 1; queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, designId, 15, value, untiValue], 'run'); } else if (parseInt(stat) === 19) { let designId = parseInt(engineId) + 2; queryDB(`INSERT OR REPLACE INTO Custom_Engines_Stats (engineId, designId, partStat, Value, unitValue) VALUES (?, ?, ?, ?, ?)`, [engineId, designId, 15, value, untiValue], 'run'); } } updateTeamsSuppliedByEngine(engineId, engineData[engineId]); } } export function check2025ModCompatibility(year_version) { ensureSeasonModTable('Custom_2025_SeasonMod', defaultSeasonModKeys2025); const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const currentDay = daySeason[0]; const currentSeason = daySeason[1]; const minDay2024 = queryDB(`SELECT MIN(Day) FROM Races WHERE SeasonID = 2024`, [], 'singleValue'); const firstRaceState2024 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2024`, [minDay2024], 'singleValue'); const maxDay2024 = queryDB(`SELECT MAX(Day) FROM Races WHERE SeasonID = 2024`, [], 'singleValue'); const lastRaceState2024 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2024`, [maxDay2024], 'singleValue'); const minDay2025 = queryDB(`SELECT MIN(Day) FROM Races WHERE SeasonID = 2025`, [], 'singleValue'); const firstRaceState2025 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2025`, [minDay2025], 'singleValue'); if (year_version !== "24") { return "NotCompatible"; } const edited = queryDB(`SELECT * FROM Custom_2025_SeasonMod WHERE value = 1`, [], 'allRows'); if (edited.length > 0) { return "AlreadyEdited"; } if (firstRaceState2024 === 0 && currentSeason === 2024) { return "Start2024"; } if (lastRaceState2024 === 2 && currentSeason === 2024) { // return "End2024"; return "NotCompatible"; } if (currentSeason === 2025 && firstRaceState2025 === 0) { // return "Direct2025"; return "NotCompatible"; } return "NotCompatible"; } export function check2026ModCompatibility(year_version) { ensureSeasonModTable('Custom_2026_SeasonMod', defaultSeasonModKeys2026); const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const currentDay = daySeason[0]; const currentSeason = daySeason[1]; const minDay2024 = queryDB(`SELECT MIN(Day) FROM Races WHERE SeasonID = 2024`, [], 'singleValue'); const firstRaceState2024 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2024`, [minDay2024], 'singleValue'); const maxDay2024 = queryDB(`SELECT MAX(Day) FROM Races WHERE SeasonID = 2024`, [], 'singleValue'); const lastRaceState2024 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2024`, [maxDay2024], 'singleValue'); const minDay2025 = queryDB(`SELECT MIN(Day) FROM Races WHERE SeasonID = 2025`, [], 'singleValue'); const firstRaceState2025 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2025`, [minDay2025], 'singleValue'); const maxDay2025 = queryDB(`SELECT MAX(Day) FROM Races WHERE SeasonID = 2025`, [], 'singleValue'); const lastRaceState2025 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2025`, [maxDay2025], 'singleValue'); const minDay2026 = queryDB(`SELECT MIN(Day) FROM Races WHERE SeasonID = 2026`, [], 'singleValue'); const firstRaceState2026 = queryDB(`SELECT State FROM Races WHERE Day = ? AND SeasonID = 2026`, [minDay2026], 'singleValue'); if (year_version !== "24") { return "NotCompatible"; } const edited = queryDB(`SELECT * FROM Custom_2026_SeasonMod WHERE value = 1`, [], 'allRows'); if (edited.length > 0) { return "AlreadyEdited"; } //get staffID's from Staff_BasicData that have IsGeneratedForCustomTeam = 1 and StaffIDs are not 552 and 553 const generatedStaff = queryDB(`SELECT StaffID FROM Staff_BasicData WHERE IsGeneratedForCustomTeam = 1 AND StaffID NOT IN (552, 553)`, [], 'allRows'); if (generatedStaff.length > 0) { return "NotCompatible"; } if (firstRaceState2024 === 0 && currentSeason === 2024) { return "Start2024"; } if (lastRaceState2024 === 2 && currentSeason === 2024) { return "End2024"; } if (firstRaceState2025 === 0 && currentSeason === 2025) { return "Start2025"; } if (lastRaceState2025 === 2 && currentSeason === 2025) { return "End2025"; } if (currentSeason === 2026 && firstRaceState2026 === 0) { return "Direct2026"; } return "NotCompatible"; } const defaultSeasonModKeys2025 = [ 'time-travel', 'extra-drivers', 'change-line-ups', 'change-stats', 'change-calendar', 'change-regulations', 'change-cfd', 'change-performance' ]; const defaultSeasonModKeys2026 = [ 'time-travel-2026', 'extra-drivers-2026', 'change-line-ups-2026', 'change-stats-2026', 'change-calendar-2026', 'change-regulations-2026', 'change-cfd-2026', 'change-performance-2026' ]; function ensureSeasonModTable(tableName, defaultKeys) { const tableExists = queryDB(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, [tableName], "singleRow"); if (!tableExists) { // Table name cannot be parameterized queryDB(`CREATE TABLE ${tableName} (key TEXT PRIMARY KEY, value TEXT)`, [], 'run'); } if (Array.isArray(defaultKeys) && defaultKeys.length > 0) { const valuesSql = defaultKeys.map((k) => `('${k}', '0')`).join(", "); // Default keys are fixed constants; safe to inline. queryDB(`INSERT OR IGNORE INTO ${tableName} (key, value) VALUES ${valuesSql}`, [], 'run'); } } export function updateTeamsSuppliedByEngine(engineId, stats) { const teamsSupplied = queryDB(`SELECT teamID FROM Custom_Engine_Allocations WHERE engineId = ?`, [engineId], 'allRows'); teamsSupplied.forEach(team => { const teamEngineId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 0`, [team[0]], 'singleValue'); const teamERSId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 1`, [team[0]], 'singleValue'); const teamGearboxId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 2`, [team[0]], 'singleValue'); for (let stat in stats) { if (parseInt(stat) < 18) { const untiValue = stats[stat]; const value = engine_unitValueToValue[stat](untiValue); queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = ?`, [value, untiValue, teamEngineId, stat], 'run'); } } const valueERS = engine_unitValueToValue[18](stats[18]); const unitValueERS = stats[18]; const valueGearbox = engine_unitValueToValue[19](stats[19]); const unitValueGearbox = stats[19]; queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = 15`, [valueERS, unitValueERS, teamERSId], 'run'); queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = 15`, [valueGearbox, unitValueGearbox, teamGearboxId], 'run'); }); } export function updateCustomConfig(data) { const alfaRomeo = data.alfa; const alphaTauri = data.alphatauri; const alpine = data.alpine; const williams = data.williams; const haas = data.haas; const redbull = data.redbull; const aston = data.aston; const primaryColor = data.primaryColor; const secondaryColor = data.secondaryColor; const difficulty = data.difficulty const playerTeam = data.playerTeam const turningPointsFrequencyPreset = data.turningPointsFrequencyPreset; const forceEditorMinimapColors = data.forceEditorMinimapColors; console.log("Updating custom config with data:", data); const replacableTeamsDict = { 9: 'alfa', 8: 'alphatauri', 5: 'alpine', 7: 'haas', 3: 'redbull', 10: 'aston', 6: 'williams', } const teamValues = { alfa: alfaRomeo, alphatauri: alphaTauri, alpine: alpine, williams: williams, haas: haas, redbull: redbull, aston: aston, }; queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('alfa', ?) `, [alfaRomeo], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('alphatauri', ?) `, [alphaTauri], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('alpine', ?) `, [alpine], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('williams', ?) `, [williams], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('haas', ?) `, [haas], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('redbull', ?) `, [redbull], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('aston', ?) `, [aston], 'run'); if (primaryColor) { queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('primaryColor', ?) `, [primaryColor], 'run'); } if (secondaryColor) { queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('secondaryColor', ?) `, [secondaryColor], 'run'); } queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('turningPointsFrequencyPreset', ?) `, [turningPointsFrequencyPreset], 'run'); queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES ('forceEditorMinimapColors', ?) `, [String(forceEditorMinimapColors)], 'run'); // for (let teamId in replacableTeamsDict) { // const teamKey = replacableTeamsDict[teamId]; // const hexValue = teamValues[teamKey]; // const color = forceEditorMinimapColors // ? hexToArgb(hexValue) // : defaultColors[teamId]; // queryDB( // `UPDATE Teams_Colours SET Colour = ? WHERE TeamID = ?`, // [color, teamId], // 'run' // ); // } if (alfaRomeo === "audi") { let color = customColors["audi"]; color = hexToDbArgb(color); const teamId = 9; queryDB( `UPDATE Teams_Colours SET Colour = ? WHERE TeamID = ?`, [color, teamId], 'run' ); } else { const teamId = 9; let color = defaultColors[teamId]; console.log("Reverting Alfa Romeo color to default:", color); queryDB( `UPDATE Teams_Colours SET Colour = ? WHERE TeamID = ?`, [color, teamId], 'run' ); } //delete the difficulty key from Custom_Save_Config every time queryDB(`DELETE FROM Custom_Save_Config WHERE key = 'difficulty'`, [], 'run'); if (parseInt(playerTeam) !== -1) { updateTeam(playerTeam) } manageDifficultyTriggers(data.triggerList) manageRefurbishTrigger(data.refurbish) const freezeDevelopment = (data.freezeDevelopment !== undefined && data.freezeDevelopment !== null) ? data.freezeDevelopment : (queryDB("SELECT name FROM sqlite_master WHERE type='trigger' AND name='freeze_development';", [], "singleValue") ? 1 : 0); editFreezeDevelopment(freezeDevelopment) const globals = getGlobals() if (globals.yearIteration === "24") { editFreezeMentality(data.frozenMentality) } } function updateTeam(teamID) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const currentDay = daySeason[0]; const metadata = getMetadata() const metaProperty = metadata.gvasMeta.Properties.Properties .filter(p => p.Name === "MetaData")[0]; queryDB(`UPDATE Player SET TeamID = ?`, [teamID], 'run'); queryDB(`UPDATE Staff_NarrativeData SET TeamID = ? WHERE GenSource = 0`, [teamID], 'run'); queryDB(`UPDATE Player_History SET EndDay = ? WHERE EndDay IS NULL`, [currentDay - 1], 'run'); queryDB(`DELETE FROM Player_History WHERE EndDay < StartDay`, [], 'run'); queryDB(`INSERT INTO Player_History VALUES (?, ?, NULL)`, [teamID, currentDay], 'run'); } export function fetchCustomConfig() { const rows = queryDB(`SELECT key, value FROM Custom_Save_Config`, [], 'allRows') || []; const config = { teams: {}, primaryColor: null, secondaryColor: null, turningPointsFrequencyPreset: defaultTurningPointsFrequencyPreset, forceEditorMinimapColors: 0, renaultEngine: 'renault' }; rows.forEach(row => { const key = row[0]; const value = row[1]; if (key === 'alphatauri' || key === 'alpine' || key === 'williams' || key === 'haas' || key === 'alfa' || key === 'redbull' || key === 'aston') { config.teams[key] = value; } else if (key === 'primaryColor') { config.primaryColor = value; } else if (key === 'secondaryColor') { config.secondaryColor = value; } else if (key === 'difficulty') { config.difficulty = value; } else if (key === 'turningPointsFrequencyPreset') { config.turningPointsFrequencyPreset = parseInt(value, 10); } else if (key === 'renaultEngine') { if (String(value).toLowerCase() === 'honda') { config.renaultEngine = 'honda'; } else { config.renaultEngine = 'renault'; } } else if (key === 'forceEditorMinimapColors') { config.forceEditorMinimapColors = parseInt(value, 10) === 1 ? 1 : 0; } }); const engine10Name = config.renaultEngine === 'honda' ? 'Honda' : 'Renault'; queryDB(`UPDATE Custom_Engines_List SET name = ? WHERE engineId = 10`, [engine10Name], 'run'); const triggers = fetchExistingTriggers() const playerTeam = fetchPlayerTeam() config.playerTeam = playerTeam config.triggerList = triggers.triggerList config.refurbish = triggers.refurbish config.frozenMentality = triggers.frozenMentality config.freezeDevelopment = triggers.freezeDevelopment if (!config.teams.williams) { config.teams.williams = 'williams'; } if (!config.teams.haas) { config.teams.haas = 'haas'; } if (!config.teams.redbull) { config.teams.redbull = 'redbull'; } if (!config.teams.aston) { config.teams.aston = 'aston'; } return config; } export function setCustomSaveConfig(key, value) { queryDB(` INSERT OR REPLACE INTO Custom_Save_Config (key, value) VALUES (?, ?) `, [key, value], 'run'); } function fetchPlayerTeam() { const playerTeam = queryDB(` SELECT TeamID FROM Player `, [], 'singleValue') || 0; return playerTeam; } export function fetch2025ModData() { ensureSeasonModTable('Custom_2025_SeasonMod', defaultSeasonModKeys2025); const rows = queryDB(`SELECT key, value FROM Custom_2025_SeasonMod`, [], 'allRows') || []; const config = {}; rows.forEach(row => { const key = row[0]; const value = row[1]; config[key] = value; }); return config; } export function fetch2026ModData() { ensureSeasonModTable('Custom_2026_SeasonMod', defaultSeasonModKeys2026); const rows = queryDB(`SELECT key, value FROM Custom_2026_SeasonMod`, [], 'allRows') || []; const config = {}; rows.forEach(row => { const key = row[0]; const value = row[1]; config[key] = value; }); // Also return the aduo turning points flag so the 2026 mods UI can restore the toggle state. const aduoEnabled = queryDB( `SELECT value FROM Custom_Save_Config WHERE key = 'aduo_tp_enabled'`, [], 'singleValue' ); config.aduo_tp_enabled = aduoEnabled ?? "0"; return config; } function createEngineMigrationTrigger() { const sql = ` DROP TRIGGER IF EXISTS trg_sync_engine_stats_on_first_full_season_day; CREATE TRIGGER trg_sync_engine_stats_on_first_full_season_day AFTER UPDATE OF Day ON Player_State WHEN NEW.CurrentSeason = OLD.CurrentSeason AND NEW.CurrentSeason > (SELECT lastSeasonApplied FROM Custom_Engine_Regulation_State WHERE id = 1) BEGIN -------------------------------------------------------------------- -- Marca la season como ya aplicada (LO PRIMERO) -------------------------------------------------------------------- UPDATE Custom_Engine_Regulation_State SET lastSeasonApplied = NEW.CurrentSeason WHERE id = 1; -------------------------------------------------------------------- -- 1) MOTOR (PartType = 0): copia todas las stats PartStat tal cual -------------------------------------------------------------------- UPDATE Parts_Designs_StatValues SET Value = ( SELECT ces.Value FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID -- motor base AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 0 LIMIT 1 ), UnitValue = ( SELECT ces.UnitValue FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 0 LIMIT 1 ) WHERE EXISTS ( SELECT 1 FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 0 ); -------------------------------------------------------------------- -- 2) CAJA DE CAMBIOS (PartType = 1): copia stats desde designId=engineId+2 -------------------------------------------------------------------- UPDATE Parts_Designs_StatValues SET Value = ( SELECT ces.Value FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 2 -- gearbox AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 1 LIMIT 1 ), UnitValue = ( SELECT ces.UnitValue FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 2 AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 1 LIMIT 1 ) WHERE EXISTS ( SELECT 1 FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 2 AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 1 ); -------------------------------------------------------------------- -- 3) ERS (PartType = 2): copia stats desde designId=engineId+1 -------------------------------------------------------------------- UPDATE Parts_Designs_StatValues SET Value = ( SELECT ces.Value FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 1 -- ERS AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 2 LIMIT 1 ), UnitValue = ( SELECT ces.UnitValue FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 1 AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 2 LIMIT 1 ) WHERE EXISTS ( SELECT 1 FROM Parts_Designs pd JOIN Custom_Engine_Allocations cea ON cea.TeamID = pd.TeamID JOIN Custom_Engines_Stats ces ON ces.EngineID = cea.EngineID AND ces.DesignID = cea.EngineID + 1 AND ces.PartStat = Parts_Designs_StatValues.PartStat WHERE pd.DesignID = Parts_Designs_StatValues.DesignID AND pd.PartType = 2 ); END; ` queryDB(sql, [], 'exec'); console.log("INSERTING TRIGGER FOR ENGINE STATS SYNC ON SEASON CHANGE"); } ================================================ FILE: src/js/backend/scriptUtils/editTeamUtils.js ================================================ import { queryDB } from "../dbManager"; export function fetchTeamData(teamID){ const levCon = queryDB(` SELECT BuildingID, DegradationValue FROM Buildings_HQ WHERE TeamID = ? `, [teamID], 'allRows') || []; const data = levCon.map(row => [row[0], parseFloat(Number(row[1]).toFixed(2))]); if (teamID == "32") data.push(["160", 1]); const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); const days = queryDB(` SELECT MIN(Day), MAX(Day) FROM Seasons_Deadlines WHERE SeasonID = ? `, [daySeason[1]], 'singleRow'); const costCap = queryDB(` SELECT SUM(value) AS Value FROM Finance_Transactions WHERE Day >= ? AND Day < ? AND AffectsCostCap = 1 AND TeamID = ? `, [days[0], days[1], teamID], 'allRows'); const teamBalance = queryDB(` SELECT Balance FROM Finance_TeamBalance WHERE TeamID = ? `, [teamID], 'singleRow'); const seasonObj = queryDB(` SELECT TargetPos FROM Board_SeasonObjectives WHERE TeamID = ? AND SeasonID = ? `, [teamID, daySeason[1]], 'singleRow'); const maxTargetYear = queryDB(` SELECT MAX(TargetEndYear) FROM Board_Objectives WHERE TeamID = ? `, [teamID], 'singleRow'); const longTermObj = queryDB(` SELECT Type, TargetEndYear FROM Board_Objectives WHERE TeamID = ? AND TargetEndYear = ? `, [teamID, maxTargetYear[0]], 'singleRow'); const playerTeam = queryDB(` SELECT TeamID FROM Player `, [], 'singleRow'); let confidence; if (playerTeam[0] == Number(teamID)) { confidence = queryDB(` SELECT Confidence FROM Board_Confidence WHERE Season = ? `, [daySeason[1]], 'singleRow') || [-1]; } else { confidence = [-1]; } const pitStats = queryDB(` SELECT StatID, Val FROM Staff_PitCrew_PerformanceStats WHERE TeamID = ? `, [teamID], 'allRows') || []; const pitDict = {}; pitStats.forEach(stat => { pitDict[stat[0]] = parseFloat(Number(stat[1]).toFixed(2)); }); const engineId = queryDB(`SELECT engineId FROM Custom_Engine_Allocations WHERE teamId = ?`, [teamID], 'singleValue'); const allEngines = queryDB(`SELECT * FROM Custom_Engine_Allocations`, [], 'allRows'); data.push(seasonObj, longTermObj, teamBalance, costCap, confidence, daySeason[1], pitDict, engineId); return data; } // manageCostCap(teamID, amount) export function manageCostCap(teamID, amount) { let remaining = parseInt(amount, 10); if (remaining > 0) { while (remaining > 0) { // Obtenemos la transacción negativa más reciente const transaction = queryDB(` SELECT ROWID, Value, Reference FROM Finance_Transactions WHERE TeamID = ? AND AffectsCostCap = 1 AND Value < 0 ORDER BY Day DESC, ROWID DESC LIMIT 1 `, [teamID], 'singleRow'); if (!transaction) { break; } else { const rowid = transaction[0]; const value = transaction[1]; // reference = transaction[2]; // no se usa directamente let amountToAdd; if ((value + remaining) <= 0) { amountToAdd = remaining; } else { amountToAdd = -value; } queryDB(` UPDATE Finance_Transactions SET Value = Value + ? WHERE ROWID = ? `, [amountToAdd, rowid], 'run'); remaining -= amountToAdd; } } } // Si remaining <= 0, insertamos una transacción que incremente el CostCap (o lo modifique negativamente) else { const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); queryDB(` INSERT INTO Finance_Transactions VALUES (?, ?, ?, 9, -1, 1) `, [teamID, daySeason[0], amount], 'run'); } } export function editTeam(info) { const daySeason = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); const teamID = info.teamID; // Actualización de Buildings_HQ info.facilities.forEach(facility => { const id = facility[0].slice(0, -1); // facility[0] podría ser "160a", por ejemplo, y con slice(0, -1) quitas el último carácter queryDB(` UPDATE Buildings_HQ SET BuildingID = ?, DegradationValue = ? WHERE TeamID = ? AND BuildingType = ? `, [facility[0], facility[1], teamID, id], 'run'); }); // Board_SeasonObjectives queryDB(` UPDATE Board_SeasonObjectives SET TargetPos = ? WHERE TeamID = ? AND SeasonID = ? `, [info.seasonObj, teamID, daySeason[1]], 'run'); // Board_Objectives (objetivo a largo plazo) const maxTargetYear = queryDB(` SELECT MAX(TargetEndYear) FROM Board_Objectives WHERE TeamID = ? `, [teamID], 'singleRow'); queryDB(` UPDATE Board_Objectives SET Type = ?, TargetEndYear = ? WHERE TeamID = ? AND TargetEndYear = ? `, [info.longTermObj, info.longTermYear, teamID, maxTargetYear[0]], 'run'); // Board_Confidence if (info.confidence !== "-1") { queryDB(` UPDATE Board_Confidence SET Confidence = ? WHERE Season = ? `, [info.confidence, daySeason[1]], 'run'); } // Finance_TeamBalance queryDB(` UPDATE Finance_TeamBalance SET Balance = ? WHERE TeamID = ? `, [info.teamBudget, teamID], 'run'); // Ajuste de CostCap manageCostCap(teamID, info.costCapEdit); // Actualizar Staff_PitCrew_PerformanceStats Object.keys(info.pitCrew).forEach(statID => { queryDB(` UPDATE Staff_PitCrew_PerformanceStats SET Val = ? WHERE TeamID = ? AND StatID = ? `, [info.pitCrew[statID], teamID, statID], 'run'); }); // La parte de manage_engine_change la manejas tú manage_engine_change(teamID, info.engine); } export function manage_engine_change(teamID, engineId) { const oldEngineId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 0`, [teamID], 'singleValue'); const oldERSId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 1`, [teamID], 'singleValue'); const oldGearboxId = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = 2`, [teamID], 'singleValue'); const nmewERSId = parseInt(engineId, 10) + 1; const newGearboxId = parseInt(engineId, 10) + 2; const newEngineStats = queryDB(`SELECT partStat, unitValue, Value FROM Custom_Engines_Stats WHERE designId = ?`, [engineId], 'allRows'); const newERSStats = queryDB(`SELECT partStat, unitValue, Value FROM Custom_Engines_Stats WHERE designId = ?`, [nmewERSId], 'singleRow'); const newGearboxStats = queryDB(`SELECT partStat, unitValue, Value FROM Custom_Engines_Stats WHERE designId = ?`, [newGearboxId], 'singleRow'); const engineStats = queryDB(`SELECT PartStat FROM Parts_Designs_StatValues WHERE DesignID = ?`, [oldEngineId], 'allRows'); engineStats.forEach(stat => { const newStat = newEngineStats.find(newStat => newStat[0] === stat[0]); if (newStat) { queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = ?`, [newStat[2], newStat[1], oldEngineId, stat[0]], 'run'); } }); queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = 15`, [newERSStats[2], newERSStats[1], oldERSId], 'run'); queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = 15`, [newGearboxStats[2], newGearboxStats[1], oldGearboxId], 'run'); if (parseInt(engineId) <= 10){ const year = queryDB(`SELECT CurrentSeason FROM Player_State`, [], 'singleValue'); const newEngineManufacturer = queryDB(`SELECT Value FROM Parts_Enum_EngineManufacturers WHERE EngineDesignID = ?`, [engineId], 'singleValue'); queryDB(`UPDATE Parts_TeamHistory SET EngineManufacturer = ? WHERE TeamID = ? AND SeasonID = ?`, [newEngineManufacturer, teamID, year], 'run'); } const existingAlloc = queryDB(`SELECT COUNT(1) FROM Custom_Engine_Allocations WHERE teamId = ?`, [teamID], 'singleValue'); if (Number(existingAlloc) > 0) { queryDB(`UPDATE Custom_Engine_Allocations SET engineId = ? WHERE teamId = ?`, [engineId, teamID], 'run'); } else { queryDB(`INSERT INTO Custom_Engine_Allocations (teamId, engineId) VALUES (?, ?)`, [teamID, engineId], 'run'); } } ================================================ FILE: src/js/backend/scriptUtils/eidtStatsUtils.js ================================================ import { queryDB } from "../dbManager"; // Constantes para referencias en la edición de mentalidad export const driverStats = [2, 3, 4, 5, 6, 7, 8, 9, 10]; export const mentalityAreas = { 0: [5, 11, 13, 9], 1: [0, 2, 6, 7, 8, 14], 2: [1, 3, 4, 12, 10] }; export const mentalityEvents = { 0: [1, 7, 10, 13, 15, 19], 1: [2, 11, 12, 14, 16, 20, 21], 2: [0, 3, 4, 5, 6, 8, 9, 17, 18] }; export const mentalityOpinions = { 0: 10, 1: 3, 2: 0, 3: -4, 4: -10 }; export const mentalityOverall = { 0: 95, 1: 79, 2: 59, 3: 24, 4: 5 }; // Editar estadísticas de un Staff (driver o staff general) export function editStats(driverID, type, stats, retirement, driverNum, wants1) { //creat sttasParasm from stats string to an array const statsParams = stats.split(" "); if (type === "0") { const isStats = queryDB(` SELECT * FROM Staff_performanceStats WHERE StaffID = ? `, [driverID], 'singleRow'); if (isStats) { queryDB(` UPDATE Staff_performanceStats SET Val = CASE StatID WHEN 2 THEN ? WHEN 3 THEN ? WHEN 4 THEN ? WHEN 5 THEN ? WHEN 6 THEN ? WHEN 7 THEN ? WHEN 8 THEN ? WHEN 9 THEN ? WHEN 10 THEN ? ELSE Val END WHERE StaffID = ? `, [ statsParams[0], statsParams[1], statsParams[2], statsParams[3], statsParams[4], statsParams[5], statsParams[6], statsParams[7], statsParams[8], driverID ], 'run'); } else { const statsArray = statsParams.slice(2, 11); statsArray.forEach((newStat, i) => { const statID = driverStats[i]; queryDB(` INSERT INTO Staff_performanceStats (StaffID, StatID, Val, Max) VALUES (?, ?, ?, 100) `, [driverID, statID, newStat], 'run'); }); } queryDB(` UPDATE Staff_DriverData SET Improvability = ?, Aggression = ? WHERE StaffID = ? `, [statsParams[9], statsParams[10], driverID], 'run'); queryDB(` UPDATE Staff_GameData SET RetirementAge = ? WHERE StaffID = ? `, [retirement, driverID], 'run'); changeDriverNumber(driverID, driverNum); queryDB(` UPDATE Staff_DriverData SET WantsChampionDriverNumber = ? WHERE StaffID = ? `, [wants1, driverID], 'run'); } else if (type === "1") { queryDB(` UPDATE Staff_performanceStats SET Val = CASE StatID WHEN 0 THEN ? WHEN 1 THEN ? WHEN 14 THEN ? WHEN 15 THEN ? WHEN 16 THEN ? WHEN 17 THEN ? ELSE Val END WHERE StaffID = ? `, [ statsParams[0], statsParams[1], statsParams[2], statsParams[3], statsParams[4], statsParams[5], driverID ], 'run'); queryDB(` UPDATE Staff_GameData SET RetirementAge = ? WHERE StaffID = ? `, [retirement, driverID], 'run'); } else if (type === "2") { queryDB(` UPDATE Staff_performanceStats SET Val = CASE StatID WHEN 13 THEN ? WHEN 25 THEN ? WHEN 43 THEN ? ELSE Val END WHERE StaffID = ? `, [ statsParams[0], statsParams[1], statsParams[2], driverID ], 'run'); queryDB(` UPDATE Staff_GameData SET RetirementAge = ? WHERE StaffID = ? `, [retirement, driverID], 'run'); } else if (type === "3") { queryDB(` UPDATE Staff_performanceStats SET Val = CASE StatID WHEN 19 THEN ? WHEN 20 THEN ? WHEN 26 THEN ? WHEN 27 THEN ? WHEN 28 THEN ? WHEN 29 THEN ? WHEN 30 THEN ? WHEN 31 THEN ? ELSE Val END WHERE StaffID = ? `, [ statsParams[0], statsParams[1], statsParams[2], statsParams[3], statsParams[4], statsParams[5], statsParams[6], statsParams[7], driverID ], 'run'); queryDB(` UPDATE Staff_GameData SET RetirementAge = ? WHERE StaffID = ? `, [retirement, driverID], 'run'); } else if (type === "4") { queryDB(` UPDATE Staff_performanceStats SET Val = CASE StatID WHEN 11 THEN ? WHEN 22 THEN ? WHEN 23 THEN ? WHEN 24 THEN ? ELSE Val END WHERE StaffID = ? `, [ statsParams[0], statsParams[1], statsParams[2], statsParams[3], driverID ], 'run'); queryDB(` UPDATE Staff_GameData SET RetirementAge = ? WHERE StaffID = ? `, [retirement, driverID], 'run'); } } export function changeDriverNumber(driverID, newNumber) { const oldNum = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder = ? `, [driverID], 'singleValue'); if (oldNum) { queryDB(` UPDATE Staff_DriverNumbers SET CurrentHolder = NULL WHERE Number = ? `, [oldNum], 'run'); } const oldHolderOfNum = queryDB(` SELECT CurrentHolder FROM Staff_DriverNumbers WHERE Number = ? `, [newNumber], 'singleValue'); if (oldHolderOfNum) { const emptyNumbers = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder IS NULL `, [], 'allRows'); if (emptyNumbers.length) { const randomNum = emptyNumbers[Math.floor(Math.random() * emptyNumbers.length)][0]; queryDB(` UPDATE Staff_DriverNumbers SET CurrentHolder = ? WHERE Number = ? `, [oldHolderOfNum, randomNum], 'run'); } } queryDB(` UPDATE Staff_DriverNumbers SET CurrentHolder = ? WHERE Number = ? `, [driverID, newNumber], 'run'); } export function editName(driverID, newName) { const parts = newName.split(" "); const newFirstName = parts[0]; const newLastName = parts.slice(1).join(" "); const stringLiteralFirstName = `[STRING_LITERAL:Value=|${newFirstName}|]`; const stringLiteralLastName = `[STRING_LITERAL:Value=|${newLastName}|]`; queryDB(` UPDATE Staff_BasicData SET FirstName = ?, LastName = ? WHERE StaffID = ? `, [stringLiteralFirstName, stringLiteralLastName, driverID], 'run'); } export function editCode(driverID, newCode) { const stringLiteralCode = `[STRING_LITERAL:Value=|${newCode}|]`; queryDB(` UPDATE Staff_DriverData SET DriverCode = ? WHERE StaffID = ? `, [stringLiteralCode, driverID], 'run'); } // Helpers de fechas export function excelToDate(excelDate) { const baseUTC = Date.UTC(1899, 11, 30); // 1899-12-30 UTC return new Date(baseUTC + excelDate * 86400000); } export function dateToExcel(date) { const baseUTC = Date.UTC(1899, 11, 30); const utcMidnight = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); return Math.floor((utcMidnight - baseUTC) / 86400000); } export function excelFromYMD(year, month, day) { const d = new Date(Date.UTC(year, month - 1, day)); return dateToExcel(d); } export function changeYearsInExcelDate(excelDate, years) { const oldDate = excelToDate(excelDate); let newYear = oldDate.getFullYear() + years; let newDate = new Date(oldDate.getTime()); newDate.setFullYear(newYear); if (newDate.getMonth() !== oldDate.getMonth()) { newDate = new Date(newYear, 1, 28); } const newExcelDate = dateToExcel(newDate); return { newDate, newExcelDate }; } export function editAge(driverID, ageGap) { const driverBirthdate = queryDB(` SELECT DOB FROM Staff_BasicData WHERE StaffID = ? `, [driverID], 'singleValue'); const { newDate, newExcelDate } = changeYearsInExcelDate(driverBirthdate, parseInt(ageGap, 10)); const y = newDate.getFullYear(); const m = newDate.getMonth() + 1; const d = newDate.getDate(); queryDB(` UPDATE Staff_BasicData SET DOB = ?, DOB_ISO = ? WHERE StaffID = ? `, [newExcelDate, `${y}-${m}-${d}`, driverID], 'run'); } export function editMentality(driverID, mentalityStr) { if (mentalityStr !== -1) { const mentalityArray = mentalityStr.split(" "); let sum = 0; mentalityArray.forEach((value, area) => { queryDB(` UPDATE Staff_Mentality_AreaOpinions SET Opinion = ? WHERE StaffID = ? AND Category = ? `, [value, driverID, area], 'run'); const statuses = mentalityAreas[area]; const events = mentalityEvents[area]; sum += parseInt(value, 10); statuses.forEach(status => { queryDB(` UPDATE Staff_Mentality_Statuses SET Opinion = ?, Value = ? WHERE StaffID = ? AND Status = ? `, [value, mentalityOpinions[value], driverID, status], 'run'); }); events.forEach(ev => { queryDB(` UPDATE Staff_Mentality_Events SET Opinion = ?, Value = ? WHERE StaffID = ? AND Event = ? `, [value, mentalityOpinions[value], driverID, ev], 'run'); }); }); const average = Math.floor(sum / 3); queryDB(` UPDATE Staff_State SET Mentality = ?, MentalityOpinion = ? WHERE StaffID = ? `, [mentalityOverall[average], average, driverID], 'run'); } } export function editRetirement(driverID, value) { queryDB(` UPDATE Staff_GameData SET Retired = ? WHERE StaffID = ? `, [value, driverID], 'run'); } export function editSuperlicense(driverID, value) { queryDB(` UPDATE Staff_DriverData SET HasSuperLicense = ?, HasRacedEnoughToJoinF1 = ? WHERE StaffID = ? `, [value, value, driverID], 'run'); } export function editMarketability(driverID, value) { queryDB(` UPDATE Staff_DriverData SET Marketability = ? WHERE StaffID = ? `, [value, driverID], 'run'); } export function setAllDriversStatsTo85() { queryDB(` UPDATE Staff_performanceStats SET Val = 85 WHERE StaffID IN (SELECT StaffID FROM Staff_DriverData) `, [], 'run'); } ================================================ FILE: src/js/backend/scriptUtils/head2head.js ================================================ import { queryDB } from "../dbManager"; // Helpers para estadísticos: const mean = (arr) => { if (!arr.length) return 0; const total = arr.reduce((acc, n) => acc + n, 0); return total / arr.length; }; const median = (arr) => { if (!arr.length) return 0; const sorted = [...arr].sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return (sorted.length % 2 === 1) ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2; }; export function fetchHead2Head(driver1ID, driver2ID, year, isCurrentYear = true) { // 1) Obtenemos todas las carreras en las que participaron ambos pilotos const racesBoth = queryDB(` SELECT RaceID FROM Races_Results WHERE Season = ? AND DriverID IN (?, ?) GROUP BY RaceID HAVING COUNT(DISTINCT DriverID) = 2 `, [year, driver1ID, driver2ID], 'allRows') || []; const raceIDs = racesBoth.map(row => row[0]); const stats = { raceH2H: [0, 0], qualiH2H: [0, 0], dnfH2H: [0, 0], podiumsH2H: [0, 0], polesH2H: [0, 0], winsH2H: [0, 0], sprintWinsH2H: [0, 0], top10H2H: [0, 0], q3H2H: [0, 0], frontRowH2H: [0, 0], pointsH2H: null, bestRace: null, bestQuali: null, raceDiffs: null, qualiDiffs: null, racePositionsMean: null, racePositionsMedian: null, qualiPositionsMean: null, qualiPositionsMedian: null, driver1: { bestRace: 21, bestQuali: 21, avgPace: [], avgQPace: [], RPositions: [], QPositions: [], startPositions: [], posGains: [] }, driver2: { bestRace: 21, bestQuali: 21, avgPace: [], avgQPace: [], RPositions: [], QPositions: [], startPositions: [], posGains: [] } }; // 3) Iteramos en cada carrera en la que compitieron ambos for (const raceID of raceIDs) { const d1_QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 0; const d2_QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 0; let d1_QRes, d2_QRes; if (isCurrentYear) { d1_QRes = queryDB(` SELECT FinishingPos FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, year, driver1ID, d1_QStage], 'singleValue') || 99; d2_QRes = queryDB(` SELECT FinishingPos FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, year, driver2ID, d2_QStage], 'singleValue') || 99; } else { d1_QRes = queryDB(`SELECT StartingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ?`, [raceID, year, driver1ID], 'singleValue') || 99; d2_QRes = queryDB(`SELECT StartingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ?`, [raceID, year, driver2ID], 'singleValue') || 99; } // --- 3.3) Quién ganó el “duelo” de qualy if (d1_QStage < d2_QStage) { stats.qualiH2H[1] += 1; } else if (d1_QStage > d2_QStage) { stats.qualiH2H[0] += 1; } else { // misma fase de qualy if (d1_QRes < d2_QRes) { stats.qualiH2H[0] += 1; } else if (d1_QRes > d2_QRes) { stats.qualiH2H[1] += 1; } } // Guardar posiciones de qualy para estadísticas finales stats.driver1.QPositions.push(d1_QRes); stats.driver2.QPositions.push(d2_QRes); // --- 3.4) Lap más rápida comparando la misma fase “mínima” const minStage = Math.min(d1_QStage, d2_QStage); const d1_qLap = queryDB(` SELECT FastestLap FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, year, driver1ID, minStage], 'singleValue') || 0; const d2_qLap = queryDB(` SELECT FastestLap FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID = ? AND QualifyingStage = ? `, [raceID, year, driver2ID, minStage], 'singleValue') || 0; if (d1_qLap !== 0 && d2_qLap !== 0) { stats.driver1.avgQPace.push(d1_qLap); stats.driver2.avgQPace.push(d2_qLap); } // --- 3.5) Poles: Q3 y posición 1 if (d1_QRes === 1 && (!isCurrentYear || d1_QStage === 3)) { stats.polesH2H[0] += 1; } if (d2_QRes === 1 && (!isCurrentYear || d2_QStage === 3)) { stats.polesH2H[1] += 1; } if (d1_QStage === 3) { stats.q3H2H[0] += 1; } if (d2_QStage === 3) { stats.q3H2H[1] += 1; } if (d1_QRes <= 2) { stats.frontRowH2H[0] += 1; } if (d2_QRes <= 2) { stats.frontRowH2H[1] += 1; } // Mejor qualifying if (d1_QRes < stats.driver1.bestQuali) { stats.driver1.bestQuali = d1_QRes; } if (d2_QRes < stats.driver2.bestQuali) { stats.driver2.bestQuali = d2_QRes; } // --- 3.6) Resultados de carrera const d1_RRes = queryDB(` SELECT FinishingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 99; const d2_RRes = queryDB(` SELECT FinishingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 99; const d1_StartPos = queryDB(` SELECT StartingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 99; const d2_StartPos = queryDB(` SELECT StartingPos FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 99; // ¿Quién terminó por delante? if (d1_RRes < d2_RRes) { stats.raceH2H[0] += 1; } else if (d1_RRes > d2_RRes) { stats.raceH2H[1] += 1; } // Wins if (d1_RRes === 1) stats.winsH2H[0] += 1; if (d2_RRes === 1) stats.winsH2H[1] += 1; // Podios if (d1_RRes <= 3) stats.podiumsH2H[0] += 1; if (d2_RRes <= 3) stats.podiumsH2H[1] += 1; // Mejor posición en carrera if (d1_RRes < stats.driver1.bestRace) { stats.driver1.bestRace = d1_RRes; } if (d2_RRes < stats.driver2.bestRace) { stats.driver2.bestRace = d2_RRes; } // Guardamos posición de carrera stats.driver1.RPositions.push(d1_RRes); stats.driver2.RPositions.push(d2_RRes); stats.driver1.startPositions.push(d1_StartPos); stats.driver2.startPositions.push(d2_StartPos); // --- 3.7) DNFs const d1_RDNF = queryDB(` SELECT DNF FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 0; const d2_RDNF = queryDB(` SELECT DNF FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 0; if (d1_RDNF === 1) stats.dnfH2H[0] += 1; if (d2_RDNF === 1) stats.dnfH2H[1] += 1; if (d1_RDNF !== 1 && d1_RRes <= 10) stats.top10H2H[0] += 1; if (d2_RDNF !== 1 && d2_RRes <= 10) stats.top10H2H[1] += 1; if (d1_RDNF !== 1) { stats.driver1.posGains.push(Number((d1_StartPos - d1_RRes).toFixed(1))); } if (d2_RDNF !== 1) { stats.driver2.posGains.push(Number((d2_StartPos - d2_RRes).toFixed(1))); } // --- 3.8) Ritmo en carrera (avg pace) si ninguno hizo DNF if (d1_RDNF !== 1 && d2_RDNF !== 1) { const d1_time = queryDB(` SELECT Time FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 0; const d2_time = queryDB(` SELECT Time FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 0; const d1_laps = queryDB(` SELECT Laps FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue') || 1; const d2_laps = queryDB(` SELECT Laps FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue') || 1; const d1_pace = Number((d1_time / d1_laps).toFixed(3)); const d2_pace = Number((d2_time / d2_laps).toFixed(3)); stats.driver1.avgPace.push(d1_pace); stats.driver2.avgPace.push(d2_pace); } // --- 3.9) SPRINT results const d1_SRes = queryDB(` SELECT FinishingPos FROM Races_Sprintresults WHERE RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, year, driver1ID], 'singleValue'); const d2_SRes = queryDB(` SELECT FinishingPos FROM Races_Sprintresults WHERE RaceID = ? AND SeasonID = ? AND DriverID = ? `, [raceID, year, driver2ID], 'singleValue'); if (d1_SRes === 1) stats.sprintWinsH2H[0] += 1; if (d2_SRes === 1) stats.sprintWinsH2H[1] += 1; } // 4) Puntos totales en el campeonato (no por carrera) const d1_Pts = queryDB(` SELECT Points FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ? AND DriverID = ? `, [year, driver1ID], 'singleValue') || 0; const d2_Pts = queryDB(` SELECT Points FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ? AND DriverID = ? `, [year, driver2ID], 'singleValue') || 0; stats.pointsH2H = [d1_Pts, d2_Pts]; const meanRd1 = Number(mean(stats.driver1.RPositions).toFixed(1)); const meanRd2 = Number(mean(stats.driver2.RPositions).toFixed(1)); const medianRd1 = median(stats.driver1.RPositions); const medianRd2 = median(stats.driver2.RPositions); const meanQd1 = Number(mean(stats.driver1.QPositions).toFixed(1)); const meanQd2 = Number(mean(stats.driver2.QPositions).toFixed(1)); const medianQd1 = median(stats.driver1.QPositions); const medianQd2 = median(stats.driver2.QPositions); const meanGrid1 = Number(mean(stats.driver1.startPositions).toFixed(1)); const meanGrid2 = Number(mean(stats.driver2.startPositions).toFixed(1)); const meanGain1 = Number(mean(stats.driver1.posGains).toFixed(1)); const meanGain2 = Number(mean(stats.driver2.posGains).toFixed(1)); const rDifferences = stats.driver1.avgPace.map((val, i) => (stats.driver2.avgPace[i] ?? 0) - val); const avg_racediff = Number(mean(rDifferences).toFixed(3)); const qDifferences = stats.driver1.avgQPace.map((val, i) => (stats.driver2.avgQPace[i] ?? 0) - val); const avg_qualidiff = Number(mean(qDifferences).toFixed(3)); // 3) Armamos el array final en el mismo orden que en tu Python: const resultList = [ stats.raceH2H, // 0) (raceH2H) stats.qualiH2H, // 1) (qualiH2H) stats.pointsH2H, // 2) (pointsH2H) stats.podiumsH2H, // 3) (podiumsH2H) [stats.driver1.bestRace, stats.driver2.bestRace], // 4) (bestRace) [stats.driver1.bestQuali, stats.driver2.bestQuali], // 5) (bestQuali) stats.dnfH2H, // 6) (dnfH2H) stats.winsH2H, // 7) (winsH2H) stats.polesH2H, // 8) (polesH2H) stats.sprintWinsH2H, // 9) (sprintWinsH2H) [-avg_racediff, avg_racediff], // 10) (-avg_racediff, avg_racediff) [-avg_qualidiff, avg_qualidiff], // 11) (-avg_qualidiff, avg_qualidiff) [meanRd1, meanRd2], // 12) (meanRd1, meanRd2) [medianRd1, medianRd2], // 13) (medianRd1, medianRd2) [meanQd1, meanQd2], // 14) (meanQd1, meanQd2) [medianQd1, medianQd2], // 15) (medianQd1, medianQd2) [meanGrid1, meanGrid2], // 16) (meanGrid1, meanGrid2) [meanGain1, meanGain2], // 17) (meanGain1, meanGain2) stats.top10H2H, // 18) (top10H2H) stats.q3H2H, // 19) (q3H2H) stats.frontRowH2H // 20) (frontRowH2H) ]; // 4) Retornamos este array en vez de 'stats' return resultList; } export function fetchHead2HeadTeam(teamID1, teamID2, year, isCurrentYear = true) { const t1 = teamID1; const t2 = teamID2; const season = year; // 1) Obtenemos todas las carreras en las que participaron ambos equipos const racesBoth = queryDB(` SELECT RaceID FROM Races_Results WHERE Season = ? AND TeamID IN (?, ?) GROUP BY RaceID HAVING COUNT(DISTINCT TeamID) = 2 `, [season, t1, t2], 'allRows') || []; const raceIDs = racesBoth.map(row => row[0]); // 2) Inicializamos contadores / arreglos const raceH2H = [0, 0]; const qualiH2H = [0, 0]; const dnfH2H = [0, 0]; const bestRace = [0, 0]; const bestQuali = [0, 0]; const pointsH2H = [0, 0]; const podiumsH2H = [0, 0]; const polesH2H = [0, 0]; const winsH2H = [0, 0]; const sprintWinsH2H = [0, 0]; const top10H2H = [0, 0]; const q3H2H = [0, 0]; const frontRowH2H = [0, 0]; let d1_BestRace = 21; let d2_BestRace = 21; let d1_BestQauli = 21; let d2_BestQauli = 21; const d1_avgPace = []; const d2_avgPace = []; const d1_avgQPace = []; const d2_avgQPace = []; const d1_RPositions = []; const d2_RPositions = []; const d1_QPositions = []; const d2_QPositions = []; const d1_startPositions = []; const d2_startPositions = []; const d1_posGains = []; const d2_posGains = []; // 3) Iteramos por cada carrera encontrada for (const raceID of raceIDs) { // 3.1) Obtenemos todos los DriverIDs de cada equipo en Quali // (En Python, se guardan como tuples y luego se hace "IN (drivers1_str)"). // En JS, construiremos la string manualmente. // Pilotos del team1 const drivers1 = queryDB(` SELECT DISTINCT DriverID FROM Races_Results WHERE RaceID = ? AND TeamID = ? `, [raceID, t1], 'allRows') || []; // Pilotos del team2 const drivers2 = queryDB(` SELECT DISTINCT DriverID FROM Races_Results WHERE RaceID = ? AND TeamID = ? `, [raceID, t2], 'allRows') || []; // Transformamos el array de arrays/tuplas en un array de IDs const drivers1IDs = drivers1.map(d => d[0]); const drivers2IDs = drivers2.map(d => d[0]); // Si no hay pilotos, podemos continuar a la siguiente carrera (para evitar queries "IN ()") if (!drivers1IDs.length || !drivers2IDs.length) { // Team 1 o Team 2 no participa en esta carrera, saltamos continue; } // 3.2) Fase de Qualy más alta para cada equipo // Note: cannot easily parameterize IN clause with array directly in all SQL dialects, // but sql.js/sqlite supports `IN (?, ?, ...)` // We will construct the placeholders string. const d1Placeholders = drivers1IDs.map(() => '?').join(','); const d2Placeholders = drivers2IDs.map(() => '?').join(','); const d1_QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d1Placeholders}) `, [raceID, season, ...drivers1IDs], 'singleValue') || 0; const d2_QStage = queryDB(` SELECT MAX(QualifyingStage) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d2Placeholders}) `, [raceID, season, ...drivers2IDs], 'singleValue') || 0; let d1_QRes, d2_QRes; // 3.3) Posición mínima en esa fase de Qualy (equivalente a "SELECT MIN(FinishingPos)") if (isCurrentYear) { d1_QRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d1Placeholders}) AND QualifyingStage = ? `, [raceID, season, ...drivers1IDs, d1_QStage], 'singleValue') || 99; d2_QRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d2Placeholders}) AND QualifyingStage = ? `, [raceID, season, ...drivers2IDs, d2_QStage], 'singleValue') || 99; } else { d1_QRes = queryDB(`SELECT MIN(StartingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d1Placeholders})`, [raceID, season, ...drivers1IDs], 'singleValue') || 99; d2_QRes = queryDB(`SELECT MIN(StartingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d2Placeholders})`, [raceID, season, ...drivers2IDs], 'singleValue') || 99; } // 3.4) Comparativa H2H de qualy if (d1_QStage < d2_QStage) { qualiH2H[1] += 1; } else if (d1_QStage > d2_QStage) { qualiH2H[0] += 1; } else { // misma fase de qualy if (d1_QRes < d2_QRes) { qualiH2H[0] += 1; } else if (d1_QRes > d2_QRes) { qualiH2H[1] += 1; } } // 3.5) Lap más rápida comparando la misma fase mínima const minQ = Math.min(d1_QStage, d2_QStage); const d1_qLap = queryDB(` SELECT FastestLap FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d1Placeholders}) AND QualifyingStage = ? `, [raceID, season, ...drivers1IDs, minQ], 'singleValue') || 0; const d2_qLap = queryDB(` SELECT FastestLap FROM Races_QualifyingResults WHERE RaceFormula = 1 AND RaceID = ? AND SeasonID = ? AND DriverID IN (${d2Placeholders}) AND QualifyingStage = ? `, [raceID, season, ...drivers2IDs, minQ], 'singleValue') || 0; if (d1_qLap !== 0 && d2_qLap !== 0) { d1_avgQPace.push(d1_qLap); d2_avgQPace.push(d2_qLap); } // Poles: si QStage = 3 y la "mejor" posición = 1 if (d1_QRes === 1 && (!isCurrentYear || d1_QStage === 3)) { polesH2H[0] += 1; } if (d2_QRes === 1 && (!isCurrentYear || d2_QStage === 3)) { polesH2H[1] += 1; } if (d1_QStage === 3) { q3H2H[0] += 1; } if (d2_QStage === 3) { q3H2H[1] += 1; } if (d1_QRes <= 2) { frontRowH2H[0] += 1; } if (d2_QRes <= 2) { frontRowH2H[1] += 1; } // Best Quali if (d1_QRes < d1_BestQauli) { d1_BestQauli = d1_QRes; } if (d2_QRes < d2_BestQauli) { d2_BestQauli = d2_QRes; } // 3.6) Resultados de carrera (usamos MIN(FinishingPos)) const d1_RRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d1Placeholders}) `, [raceID, season, ...drivers1IDs], 'singleValue') || 99; const d2_RRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d2Placeholders}) `, [raceID, season, ...drivers2IDs], 'singleValue') || 99; const d1_StartPos = queryDB(` SELECT MIN(StartingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d1Placeholders}) `, [raceID, season, ...drivers1IDs], 'singleValue') || 99; const d2_StartPos = queryDB(` SELECT MIN(StartingPos) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d2Placeholders}) `, [raceID, season, ...drivers2IDs], 'singleValue') || 99; // Wins if (d1_RRes === 1) winsH2H[0] += 1; if (d2_RRes === 1) winsH2H[1] += 1; // Race H2H if (d1_RRes < d2_RRes) { raceH2H[0] += 1; } else if (d1_RRes > d2_RRes) { raceH2H[1] += 1; } d1_RPositions.push(d1_RRes); d2_RPositions.push(d2_RRes); d1_startPositions.push(d1_StartPos); d2_startPositions.push(d2_StartPos); // Podios if (d1_RRes <= 3) podiumsH2H[0] += 1; if (d2_RRes <= 3) podiumsH2H[1] += 1; if (d1_RRes <= 10) top10H2H[0] += 1; if (d2_RRes <= 10) top10H2H[1] += 1; // Best Race if (d1_RRes < d1_BestRace) { d1_BestRace = d1_RRes; } if (d2_RRes < d2_BestRace) { d2_BestRace = d2_RRes; } // 3.7) DNF => sumamos const d1_RDNF = queryDB(` SELECT SUM(DNF) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d1Placeholders}) `, [raceID, season, ...drivers1IDs], 'singleValue') || 0; const d2_RDNF = queryDB(` SELECT SUM(DNF) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d2Placeholders}) `, [raceID, season, ...drivers2IDs], 'singleValue') || 0; dnfH2H[0] += d1_RDNF; dnfH2H[1] += d2_RDNF; if (d1_RDNF === 0) { d1_posGains.push(Number((d1_StartPos - d1_RRes).toFixed(1))); } if (d2_RDNF === 0) { d2_posGains.push(Number((d2_StartPos - d2_RRes).toFixed(1))); } // 3.8) Ritmo de carrera (si al menos un piloto del equipo no hizo DNF) const d1_racePaceStats = queryDB(` SELECT COUNT(*), AVG(Time), AVG(Laps) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d1Placeholders}) AND DNF = 0 `, [raceID, season, ...drivers1IDs], 'singleRow') || [0, 0, 0]; if (d1_racePaceStats[0] > 0) { // [0] is COUNT const avgTime = d1_racePaceStats[1]; const avgLaps = d1_racePaceStats[2]; if (avgLaps && avgTime) { const pace = Number((avgTime / avgLaps).toFixed(3)); d1_avgPace.push(pace); } } const d2_racePaceStats = queryDB(` SELECT COUNT(*), AVG(Time), AVG(Laps) FROM Races_Results WHERE RaceID = ? AND Season = ? AND DriverID IN (${d2Placeholders}) AND DNF = 0 `, [raceID, season, ...drivers2IDs], 'singleRow') || [0, 0, 0]; if (d2_racePaceStats[0] > 0) { // [0] is COUNT const avgTime = d2_racePaceStats[1]; const avgLaps = d2_racePaceStats[2]; if (avgLaps && avgTime) { const pace = Number((avgTime / avgLaps).toFixed(3)); d2_avgPace.push(pace); } } // 3.9) Sprint results (MIN FinishingPos) const d1_SRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_Sprintresults WHERE RaceID = ? AND SeasonID = ? AND DriverID IN (${d1Placeholders}) `, [raceID, season, ...drivers1IDs], 'singleValue'); const d2_SRes = queryDB(` SELECT MIN(FinishingPos) FROM Races_Sprintresults WHERE RaceID = ? AND SeasonID = ? AND DriverID IN (${d2Placeholders}) `, [raceID, season, ...drivers2IDs], 'singleValue'); if (d1_SRes === 1) { sprintWinsH2H[0] += 1; } if (d2_SRes === 1) { sprintWinsH2H[1] += 1; } d1_QPositions.push(d1_QRes); d2_QPositions.push(d2_QRes); } // 4) Puntos de cada equipo en el campeonato (TeamStandings) const d1_Pts = queryDB(` SELECT Points FROM Races_TeamStandings WHERE RaceFormula = 1 AND SeasonID = ? AND TeamID = ? `, [season, t1], 'singleValue') || 0; const d2_Pts = queryDB(` SELECT Points FROM Races_TeamStandings WHERE RaceFormula = 1 AND SeasonID = ? AND TeamID = ? `, [season, t2], 'singleValue') || 0; pointsH2H[0] = d1_Pts; pointsH2H[1] = d2_Pts; bestRace[0] = d1_BestRace; bestRace[1] = d2_BestRace; bestQuali[0] = d1_BestQauli; bestQuali[1] = d2_BestQauli; // 5) Calculamos la diferencia media de ritmo (race y quali). // rDifferences = (d2_avg - d1_avg) para cada par const rDifferences = d1_avgPace.map((val, i) => { const d2Val = d2_avgPace[i] || 0; return d2Val - val; }); const qDifferences = d1_avgQPace.map((val, i) => { const d2Val = d2_avgQPace[i] || 0; return d2Val - val; }); // Helpers para la media const mean = (arr) => { if (!arr.length) return 0; const sum = arr.reduce((acc, num) => acc + num, 0); return sum / arr.length; }; const avg_racediff = Number(mean(rDifferences).toFixed(3)); const avg_qualidiff = Number(mean(qDifferences).toFixed(3)); const meanRd1 = Number(mean(d1_RPositions).toFixed(1)); const meanRd2 = Number(mean(d2_RPositions).toFixed(1)); const medianRd1 = median(d1_RPositions); const medianRd2 = median(d2_RPositions); const meanQd1 = Number(mean(d1_QPositions).toFixed(1)); const meanQd2 = Number(mean(d2_QPositions).toFixed(1)); const medianQd1 = median(d1_QPositions); const medianQd2 = median(d2_QPositions); const meanGrid1 = Number(mean(d1_startPositions).toFixed(1)); const meanGrid2 = Number(mean(d2_startPositions).toFixed(1)); const meanGain1 = Number(mean(d1_posGains).toFixed(1)); const meanGain2 = Number(mean(d2_posGains).toFixed(1)); const resultList = [ raceH2H, qualiH2H, pointsH2H, podiumsH2H, bestRace, bestQuali, dnfH2H, winsH2H, polesH2H, sprintWinsH2H, [-avg_racediff, avg_racediff], [-avg_qualidiff, avg_qualidiff], [meanRd1, meanRd2], [medianRd1, medianRd2], [meanQd1, meanQd2], [medianQd1, medianQd2], [meanGrid1, meanGrid2], [meanGain1, meanGain2], top10H2H, q3H2H, frontRowH2H ]; // 7) Retornamos el array final return resultList; } ================================================ FILE: src/js/backend/scriptUtils/modUtils.js ================================================ import { getGlobals } from "../commandGlobals.js"; import { queryDB, setMetaData, getMetadata } from "../dbManager.js"; import { excelToDate, dateToExcel, changeDriverNumber, excelFromYMD } from "./eidtStatsUtils.js"; import { editContract, fireDriver, hireDriver, rearrangeDriverEngineerPairings, removeFutureContract } from "./transferUtils.js"; import { editSuperlicense } from "./eidtStatsUtils.js"; import { getBestParts, applyBoostToCarStats, getTyreDegStats, updateTyreDegStats, getPerformanceAllTeams, applyExpertiseBoost } from "./carAnalysisUtils.js"; import contracts from "../../../data/contracts_2025.json" import changes from "../../../data/2025_changes.json" import changes2026 from "../../../data/2026_changes.json" import tables2026 from "../../../data/tables_2026.json" import { editEngines, fetchEngines, setCustomSaveConfig, updateCustomEngines, wipeTableAndRefill } from "./dbUtils.js"; import { update } from "idb-keyval"; import { manage_engine_change } from "./editTeamUtils.js"; let staffIDChanges = {}; let newStaffIDCounter = 800; export function resetStaffIDChanges() { staffIDChanges = {}; newStaffIDCounter = 800; } export function timeTravelWithData(dayNumber, extend = false, mod = "2025") { let metadata, version; metadata = getMetadata(); version = metadata.gvasHeader.SaveGameVersion; let yearIteration = getGlobals().yearIteration; const daySeasonRow = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); const vanillaSeason = daySeasonRow[1]; // ORIGINAL SEASON const VanillaDay = daySeasonRow[0]; const wayBackSeason = excelToDate(dayNumber).getFullYear(); // SEASON TO TIME TRAVEL TO let moddedDayNumber; if (mod === "2025") { moddedDayNumber = dateToExcel(new Date(`${wayBackSeason}-12-29`)); } else if (mod === "2026") { moddedDayNumber = dateToExcel(new Date(`${wayBackSeason}-12-28`)); } const seasonStartDayNumber = excelFromYMD(wayBackSeason, 1, 1); const vanillaDayNumber = excelFromYMD(vanillaSeason, 1, 1); let dd = vanillaDayNumber - seasonStartDayNumber; // DAY DIFFERENCE BETWEEN THE START OF THE VANILLA SEASON AND THE START OF THE WAY BACK SEASON const yd = vanillaSeason - wayBackSeason; const metaProperty = metadata.gvasMeta.Properties.Properties .filter(p => p.Name === "MetaData")[0]; metaProperty.Properties[0].Properties.forEach(x => { if (x.Name === "Day") { x.Property = moddedDayNumber; } }); queryDB(`UPDATE Player_State SET Day = ?`, [moddedDayNumber], 'run'); queryDB(`UPDATE Player_State SET CurrentSeason = ?`, [wayBackSeason], 'run'); queryDB(` UPDATE Calendar_LastActivityDates SET LastScoutDate = ?, LastEngineerDate = ?, LastDesignProjectDate = ?, LastResearchProjectDate = ? `, [seasonStartDayNumber, seasonStartDayNumber, seasonStartDayNumber, seasonStartDayNumber], 'run'); // Ajuste en las tablas de partes/diseños queryDB(`UPDATE Parts_Designs SET DayCreated = DayCreated - ? WHERE DayCreated > 0`, [dd], 'run'); queryDB(`UPDATE Parts_Designs SET DayCompleted = DayCompleted - ? WHERE DayCompleted > 0`, [dd], 'run'); queryDB(`UPDATE Parts_Designs SET ValidFrom = ValidFrom - ?`, [yd], 'run'); // Elimino Sponsorship_GuaranteesAndIncentives if (yearIteration === "23") { queryDB(`DELETE FROM Sponsorship_GuaranteesAndIncentives`, [], 'run'); } // Elimino temporadas y carreras de otros años queryDB(`DELETE FROM Races WHERE SeasonID != ?`, [vanillaSeason], 'run'); queryDB(`DELETE FROM Seasons WHERE SeasonID != ?`, [vanillaSeason], 'run'); // Si extiendo, cambio el estado de la temporada if (extend) { queryDB(` UPDATE Races SET SeasonID = ?, State = 2 WHERE SeasonID = ? `, [wayBackSeason, vanillaSeason], 'run'); } else { //with State = 2 we move the races to the actual days of the new season but mark them as completed queryDB(`UPDATE Races SET SeasonID = ?, Day = Day - ?, State = 2 WHERE SeasonID = ?`, [wayBackSeason, dd, vanillaSeason], 'run') } // ============================================================ // FIX GOD BISESTOS: Seasons_Deadlines (NO usar dd para Day) // ============================================================ // Primero leo las filas ANTES de actualizar SeasonID, para saber qué mover const deadlinesRows = queryDB( `SELECT rowid AS _rowid_, SeasonID, Day FROM Seasons_Deadlines WHERE SeasonID = ?`, [vanillaSeason], 'allRows' ); // Actualizo SeasonID como antes (esto sí puede ir con yd) queryDB(` UPDATE Seasons_Deadlines SET SeasonID = SeasonID - ? WHERE SeasonID = ? `, [yd, vanillaSeason], 'run'); // Recalculo Day preservando mes/día (UTC) para evitar off-by-one por bisiestos const excelEpochUTC = Date.UTC(1899, 11, 30); for (const row of deadlinesRows) { const rowid = row._rowid_ ?? row[0]; const originalDay = row.Day ?? row[2]; if (originalDay == null || Number(originalDay) <= 0) continue; const d = new Date(excelEpochUTC + Number(originalDay) * 86400000); // Mantener mes/día, pero cambiar el año relativo al salto de temporadas const targetYear = d.getUTCFullYear() - yd; // equivalente a + (wayBackSeason - vanillaSeason) // Clamp por si existiera 29 feb -> 28 feb en año no bisiesto const maxDayInTargetMonth = new Date(Date.UTC(targetYear, d.getUTCMonth() + 1, 0)).getUTCDate(); const safeDay = Math.min(d.getUTCDate(), maxDayInTargetMonth); const remappedUTC = Date.UTC(targetYear, d.getUTCMonth(), safeDay); const remappedExcel = Math.floor((remappedUTC - excelEpochUTC) / 86400000); queryDB( `UPDATE Seasons_Deadlines SET Day = ? WHERE rowid = ?`, [remappedExcel, rowid], 'run' ); } // Ajustes para versiones >= 3 if (version >= 3) { queryDB(`UPDATE Player SET FirstGameDay = ?`, [moddedDayNumber], 'run'); queryDB(`UPDATE Player_Record SET StartSeason = ?`, [wayBackSeason], 'run'); queryDB(`UPDATE Player_History SET StartDay = ?`, [seasonStartDayNumber], 'run'); queryDB(`UPDATE Staff_PitCrew_DevelopmentPlan SET Day = Day - ? WHERE Day > 40000`, [dd], 'run'); queryDB(`UPDATE Onboarding_Tutorial_RestrictedActions SET TutorialIsActiveSetting = 0`, [], 'run'); } // Ajustes para versión === 2 if (version === 2) { queryDB(`UPDATE Onboarding_Tutorial_RestrictedActions SET Allowed = 0`, [], 'run'); } let prestigeTableName = "Board_Prestige"; if (yearIteration === "24") { prestigeTableName = "Board_TeamRating"; } // Pares de tablas y columnas a modificar const moddingPairs = [ { table: ["Staff_Contracts"], modDay: ["StartDay"], modSeason: ["EndSeason"], }, { table: ["Staff_CareerHistory"], modDay: ["StartDay", "EndDay"], modSeason: [], }, { table: ["Mail_EventPool_Cooldown"], modDay: ["NextTriggerDay"], modSeason: [], versions: [3], }, { table: ["Board_Confidence"], modDay: [], modSeason: ["Season"], }, { table: ["Board_Objectives"], modDay: [], modSeason: ["StartYear", "TargetEndYear"], }, { table: [ prestigeTableName, "Board_SeasonObjectives", "Seasons", "Parts_TeamHistory", "Races_Strategies", "Staff_Driver_RaceRecordPerSeason" ], modDay: [], modSeason: ["SeasonID"], }, { table: ["Mail_Inbox"], modDay: ["Day"], modSeason: [], }, { table: ["Races_DriverStandings", "Races_TeamStandings"], modDay: [], modSeason: ["SeasonID"], }, { table: ["Races_PitCrewStandings"], modDay: [], modSeason: ["SeasonID"], versions: [3], }, ]; // Aplico modificaciones en masa for (const pair of moddingPairs) { if (pair.versions && !pair.versions.includes(version)) { continue; } for (const table of pair.table) { for (const md of pair.modDay) { queryDB(`UPDATE ${table} SET ${md} = ${md} - ?`, [dd], 'run'); } for (const ms of pair.modSeason) { queryDB(`UPDATE ${table} SET ${ms} = ${ms} - ? WHERE ${ms} = ?`, [yd, vanillaSeason], 'run'); } } } // Obtengo la lista de tablas const allTables = queryDB( "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name ASC", [], "allRows" ); for (const row of allTables) { const table = row[0]; if (table.startsWith("Teams_RaceRecord")) { queryDB(`DELETE FROM ${table}`, [], 'run'); } if (table === "Races_Results") { queryDB(`DELETE FROM ${table} WHERE Season != ?`, [vanillaSeason], 'run'); queryDB(`UPDATE ${table} SET Season = Season - ? WHERE Season = ?`, [yd, vanillaSeason], 'run'); } else if (table.startsWith("Races") && table.endsWith("Results")) { queryDB(`DELETE FROM ${table} WHERE SeasonID != ?`, [vanillaSeason], 'run'); queryDB(`UPDATE ${table} SET SeasonID = SeasonID - ? WHERE SeasonID = ?`, [yd, vanillaSeason], 'run'); } } if (extend) { queryDB(`UPDATE Staff_Contracts SET EndSeason = EndSeason + 1`, [], 'run'); } setMetaData(metadata) if (mod === "2025") { updateSeasonModTable("time-travel", 1, "2025"); } else if (mod === "2026") { updateSeasonModTable("time-travel-2026", 1, "2026"); } } export function changeDriverLineUps() { if (contracts.Updates && Array.isArray(contracts.Updates)) { contracts.Updates.forEach((update) => { const hasContractWithTeam32 = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = 32`, [update.DriverID], "singleRow"); if (!hasContractWithTeam32) { const { DriverID, salary, EndSeason, StartingBonus, RaceBonus, RaceBonusTargetPos } = update; editContract( DriverID, salary, EndSeason, StartingBonus, RaceBonus, RaceBonusTargetPos ); } }); } if (contracts.Fires && Array.isArray(contracts.Fires)) { contracts.Fires.forEach((fire) => { const { DriverID, TeamID, ExtraTeamID, Retire, PosInTeam } = fire; if (TeamID !== null && TeamID !== undefined) { modFire(DriverID, TeamID, PosInTeam); } if (Retire !== null && Retire !== undefined) { queryDB(`UPDATE Staff_GameData SET Retired = 0 WHERE StaffID = ?`, [DriverID], 'run'); } }); } if (contracts.Hires && Array.isArray(contracts.Hires)) { contracts.Hires.forEach((hire) => { const hasContractWithTeam32 = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = 32`, [hire.DriverID], "singleRow"); if (!hasContractWithTeam32) { const { DriverID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, BreakoutClause, GrantsSuperLicense } = hire; if (GrantsSuperLicense) { editSuperlicense(DriverID, GrantsSuperLicense); } const contractExists = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`, [DriverID, TeamID], "singleRow"); if (!contractExists) { hireDriver( "manual", DriverID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, "24" ); } } }); } if (contracts.StaffHires && Array.isArray(contracts.StaffHires)) { contracts.StaffHires.forEach((hire) => { const hasContractWithTeam32 = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = 32`, [hire.StaffID], "singleRow"); if (!hasContractWithTeam32) { const { StaffID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, BreakoutClause } = hire; hireDriver( "manual", StaffID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, "24" ); } }); } const f1Workers = queryDB(`SELECT StaffID FROM Staff_Contracts WHERE TeamID <= 10 AND PosInTeam <= 2`, [], "allRows"); f1Workers.forEach((worker) => { removeFutureContract(worker[0]); }); changeDriverNumber(95, 30); updateSeasonModTable("change-line-ups", 1, "2025"); } export function modFire(driverID, teamID, PosInTeam) { const isInTeam = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`, [driverID, teamID], "singleRow"); if (isInTeam) { const position = queryDB(`SELECT PosInTeam FROM Staff_Contracts WHERE StaffID = ?`, [driverID], "singleValue"); queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`, [driverID, teamID], 'run'); if (position < 3) { queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`, [driverID], 'run'); } const engineerID = queryDB( `SELECT RaceEngineerID FROM Staff_RaceEngineerDriverAssignments WHERE IsCurrentAssignment = 1 AND DriverID = ?`, [driverID], "singleValue" ); if (engineerID) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ? AND DriverID = ?`, [engineerID, driverID], 'run'); } } else { const staffType = queryDB(`SELECT StaffType FROM Staff_GameData WHERE StaffID = ?`, [driverID], "singleValue"); const replacement = queryDB(`SELECT con.StaffID FROM Staff_Contracts con JOIN Staff_GameData gd ON con.StaffID = gd.StaffID WHERE gd.StaffType = ? AND con.TeamID = ? AND con.PosInTeam = ? AND con.ContractType = 0`, [staffType, teamID, PosInTeam], "singleValue"); if (replacement) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`, [replacement, teamID], 'run'); if (PosInTeam < 3) { queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`, [replacement], 'run'); } const engineerID = queryDB( `SELECT RaceEngineerID FROM Staff_RaceEngineerDriverAssignments WHERE IsCurrentAssignment = 1 AND DriverID = ?`, [replacement], "singleValue" ); if (engineerID) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ? AND DriverID = ?`, [engineerID, replacement], 'run'); } } } } export function changeStats() { if (!changes.Stats || !Array.isArray(changes.Stats)) { console.log("No stats found"); } else { for (const entry of changes.Stats) { const { StaffID, StatID, Val, Max } = entry; queryDB(` UPDATE Staff_PerformanceStats SET Val = ?, Max = ? WHERE StaffID = ? AND StatID = ? `, [Val, Max, StaffID, StatID], 'run'); } } updateSeasonModTable("change-stats", 1, "2025"); } export function changeDriverEngineerPairs() { if (!changes.TeamLineUps || !Array.isArray(changes.TeamLineUps)) { console.error("No driver-engineer pairs"); } else { for (const entry of changes.TeamLineUps) { const { TeamID, Driver1, Engineer1, Driver2, Engineer2 } = entry; const areAllInSameTeam = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND PosInTeam <= 2 AND ContractType = 0`, [Driver1, TeamID], "singleRow") && queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND PosInTeam <= 2 AND ContractType = 0`, [Driver2, TeamID], "singleRow") && queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND PosInTeam <= 2 AND ContractType = 0`, [Engineer1, TeamID], "singleRow") && queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND PosInTeam <= 2 AND ContractType = 0`, [Engineer2, TeamID], "singleRow"); if (areAllInSameTeam) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE DriverID = ? OR DriverID = ?`, [Driver1, Driver2], 'run'); queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ? OR RaceEngineerID = ?`, [Engineer1, Engineer2], 'run'); let driver1Engineer1 = queryDB(`SELECT * FROM Staff_RaceEngineerDriverAssignments WHERE DriverID = ? AND RaceEngineerID = ?`, [Driver1, Engineer1], "singleRow"); let driver2Engineer2 = queryDB(`SELECT * FROM Staff_RaceEngineerDriverAssignments WHERE DriverID = ? AND RaceEngineerID = ?`, [Driver2, Engineer2], "singleRow"); if (driver1Engineer1 && driver1Engineer1.length > 0) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 1 WHERE DriverID = ? AND RaceEngineerID = ?`, [Driver1, Engineer1], 'run'); } else { queryDB(`INSERT INTO Staff_RaceEngineerDriverAssignments (RaceEngineerID, DriverID, DaysTogether, RelationshipLevel, IsCurrentAssignment) VALUES (?, ?, 0, 0, 1)`, [Engineer1, Driver1], 'run'); } if (driver2Engineer2 && driver2Engineer2.length > 0) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 1 WHERE DriverID = ? AND RaceEngineerID = ?`, [Driver2, Engineer2], 'run'); } else { queryDB(`INSERT INTO Staff_RaceEngineerDriverAssignments (RaceEngineerID, DriverID, DaysTogether, RelationshipLevel, IsCurrentAssignment) VALUES (?, ?, 0, 0, 1)`, [Engineer2, Driver2], 'run'); } } else { rearrangeDriverEngineerPairings(TeamID) } } } } export function change2024Standings(mod = "2025") { if (!changes.DriverStandings || !Array.isArray(changes.DriverStandings)) { console.error("No driver standings found"); } else { for (const entry of changes.DriverStandings) { const { DriverID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID } = entry; queryDB(` UPDATE Races_DriverStandings SET LastPointsChange = ?, LastPositionChange = ?, Points = ?, Position = ? WHERE DriverID = ? AND RaceFormula = ? AND SeasonID = ? `, [LastPointsChange, LastPositionChange, Points, Position, DriverID, RaceFormula, SeasonID], 'run'); } } if (!changes.TeamStandings || !Array.isArray(changes.TeamStandings)) { console.error("No team standings found"); } else { queryDB(`DELETE FROM Races_TeamStandings WHERE RaceFormula = 1 AND SeasonID = 2024`, [], 'run'); for (const entry of changes.TeamStandings) { const { LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID, TeamID } = entry; queryDB(` INSERT INTO Races_TeamStandings (TeamID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID) VALUES (?, ?, ?, ?, ?, ?, ?) `, [TeamID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID], 'run'); } } if (mod === "2025") { updateSeasonModTable("change-cfd", 1, "2025"); } } export function manageFeederSeries() { if (!contracts.FeederSeries || !Array.isArray(contracts.FeederSeries)) { console.error("No feeder series found"); } else { queryDB(`DELETE FROM Staff_Contracts WHERE PosInTeam <= 3 AND StaffID IN (SELECT StaffID FROM Staff_DriverData) AND TeamID BETWEEN 11 AND 31`, [], 'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = NULL`, [], 'run') const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], "singleRow"); const day = daySeason[0]; for (const entry of contracts.FeederSeries) { const { DriverID, TeamID, PosInTeam, Salary, EndSeason } = entry; queryDB(`INSERT INTO Staff_Contracts (StaffID, ContractType, TeamID, PosInTeam, StartDay, EndSeason, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, BreakoutClause, AffiliateDualRoleClause) VALUES (?, 0, ?, ?, ?, ?, ?, 0, 0, 1, 0.5, 0)`, [DriverID, TeamID, PosInTeam, day, EndSeason, Salary], 'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = ?, AssignedCarNumber = NULL, LastKnownDriverNumber = NULL WHERE StaffID = ?`, [PosInTeam, DriverID], 'run'); const driverTeamRaceEngineers = queryDB(`SELECT gd.StaffID FROM Staff_GameData gd JOIN Staff_Contracts sc ON gd.StaffID = sc.StaffID WHERE gd.StaffType = 2 AND gd.StaffID IN (SELECT StaffID FROM Staff_Contracts WHERE TeamID = ?)`, [TeamID], "allRows"); let newRaceEngineer = driverTeamRaceEngineers[0][0]; let pairExists = queryDB(`SELECT * FROM Staff_RaceEngineerDriverAssignments WHERE RaceEngineerID = ? AND DriverID = ?`, [newRaceEngineer, DriverID], "singleRow"); if (pairExists && pairExists.length > 0) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 1 WHERE RaceEngineerID = ? AND DriverID = ?`, [newRaceEngineer, DriverID], 'run'); } else { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ?`, [newRaceEngineer], 'run'); queryDB(`INSERT INTO Staff_RaceEngineerDriverAssignments (RaceEngineerID, DriverID, DaysTogether, RelationshipLevel, IsCurrentAssignment) VALUES (?, ?, 0, 0, 1)`, [newRaceEngineer, DriverID], 'run'); } } } } export function manageAffiliates() { queryDB(` DELETE FROM Staff_Contracts WHERE PosInTeam > 2 AND StaffID IN (SELECT StaffID FROM Staff_DriverData) `, [], 'run'); if (contracts.Affiliates && Array.isArray(contracts.Affiliates)) { contracts.Affiliates.forEach((affiliate) => { const hasContractWithTeam32 = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND TeamID = 32`, [affiliate.DriverID], "singleRow"); const isFullTimeDriver = queryDB(`SELECT * FROM Staff_Contracts WHERE StaffID = ? AND PosInTeam <= 2 AND (TeamID <= 10 OR TeamID == 32)`, [affiliate.DriverID], "singleRow"); if (!hasContractWithTeam32 && !isFullTimeDriver) { const { DriverID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, BreakoutClause } = affiliate; hireDriver( "manual", DriverID, TeamID, PosInTeam, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos, EndSeason, "24" ); } }); } } export function manageStandings() { queryDB(`DELETE FROM Races_DriverStandings WHERE RaceFormula = 2 AND SeasonID = 2025`, [], 'run'); queryDB(`DELETE FROM Races_DriverStandings WHERE RaceFormula = 3 AND SeasonID = 2025`, [], 'run'); queryDB(` DELETE FROM Races_DriverStandings WHERE SeasonID = 2025 AND RaceFormula = 1 AND NOT EXISTS ( SELECT 1 FROM Staff_Contracts sc WHERE sc.StaffID = Races_DriverStandings.DriverID AND sc.PosInTeam <= 2 ); `, [], 'run'); let position = 1; let f1_grid = queryDB(`SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = 2025`, [], "allRows"); f1_grid.forEach((driver) => { queryDB(`UPDATE Races_DriverStandings SET Position = ? WHERE DriverID = ? AND RaceFormula = 1 AND SeasonID = 2025`, [position, driver[0]], 'run'); position++; }); queryDB(` INSERT INTO Races_DriverStandings ( SeasonID, DriverID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula ) SELECT 2025 AS SeasonID, sc.StaffID AS DriverID, 0 AS Points, 0 AS Position, 0 AS LastPointsChange, 0 AS LastPositionChange, 2 AS RaceFormula FROM Staff_Contracts sc INNER JOIN Staff_GameData sgd ON sc.StaffID = sgd.StaffID WHERE sgd.StaffType = 0 AND sc.TeamID BETWEEN 11 AND 21 `, [], 'run'); position = 1; let f2_grid = queryDB(`SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = 2 AND SeasonID = 2025`, [], "allRows"); f2_grid.forEach((driver) => { queryDB(`UPDATE Races_DriverStandings SET Position = ? WHERE DriverID = ? AND RaceFormula = 2 AND SeasonID = 2025`, [position, driver[0]], 'run'); queryDB(`INSERT INTO Races_DriverStandings (SeasonID, DriverID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) VALUES (2024, ?, 0, ?, 0, 0, 2)`, [driver[0], position], 'run'); position++; }); // 2) TeamID entre 22 y 31 => RaceFormula = 3 queryDB(` INSERT INTO Races_DriverStandings ( SeasonID, DriverID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula ) SELECT 2025 AS SeasonID, sc.StaffID AS DriverID, 0 AS Points, 0 AS Position, 0 AS LastPointsChange, 0 AS LastPositionChange, 3 AS RaceFormula FROM Staff_Contracts sc INNER JOIN Staff_GameData sgd ON sc.StaffID = sgd.StaffID WHERE sgd.StaffType = 0 AND sc.TeamID BETWEEN 22 AND 31 `, [], 'run'); position = 1; let f3_grid = queryDB(`SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = 3 AND SeasonID = 2025`, [], "allRows"); f3_grid.forEach((driver) => { queryDB(`UPDATE Races_DriverStandings SET Position = ? WHERE DriverID = ? AND RaceFormula = 3 AND SeasonID = 2025`, [position, driver[0]], 'run'); queryDB(`INSERT INTO Races_DriverStandings (SeasonID, DriverID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) VALUES (2024, ?, 0, ?, 0, 0, 3)`, [driver[0], position], 'run'); position++; }); position = 1; let f2_teams = queryDB(`SELECT TeamID FROM Races_TeamStandings WHERE RaceFormula = 2 AND SeasonID = 2025`, [], "allRows"); f2_teams.forEach((team) => { queryDB(`UPDATE Races_TeamStandings SET Position = ? WHERE TeamID = ? AND RaceFormula = 2 AND SeasonID = 2025`, [position, team[0]], 'run'); queryDB(`INSERT INTO Races_TeamStandings (SeasonID, TeamID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) VALUES (2024, ?, 0, ?, 0, 0, 2)`, [team[0], position], 'run'); position++; }); position = 1; let f3_teams = queryDB(`SELECT TeamID FROM Races_TeamStandings WHERE RaceFormula = 3 AND SeasonID = 2025`, [], "allRows"); f3_teams.forEach((team) => { queryDB(`UPDATE Races_TeamStandings SET Position = ? WHERE TeamID = ? AND RaceFormula = 3 AND SeasonID = 2025`, [position, team[0]], 'run'); queryDB(`INSERT INTO Races_TeamStandings (SeasonID, TeamID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) VALUES (2024, ?, 0, ?, 0, 0, 3)`, [team[0], position], 'run'); position++; }); //copy all races_pitcrewstandings form 2025 to 2024 queryDB(`INSERT INTO Races_PitCrewStandings (SeasonID, TeamID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) SELECT 2024, TeamID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula FROM Races_PitCrewStandings WHERE SeasonID = 2025`, [], 'run'); } export function changeRaces(type) { if (!changes.Calendar || !Array.isArray(changes.Calendar)) { console.log("No calendar data found"); } else { if (type === "Start2024" || type === "End2024") { let maxRaceId = queryDB(`SELECT MAX(RaceID) FROM Races`, [], "singleRow")[0]; let newRaceId = maxRaceId + 1; for (const entry of changes.Calendar) { const { TrackID, Day, WeekendType } = entry; queryDB(` INSERT INTO Races ( RaceID, SeasonID, TrackID, Day, State, RainPractice, TemperaturePractice, WeatherStatePractice, RainQualifying, TemperatureQualifying, WeatherStateQualifying, RainRace, TemperatureRace, WeatherStateRace, WeekendType ) SELECT ? AS RaceID, 2025 AS SeasonID, r.TrackID, ? AS Day, 0 AS State, r.RainPractice, r.TemperaturePractice, r.WeatherStatePractice, r.RainQualifying, r.TemperatureQualifying, r.WeatherStateQualifying, r.RainRace, r.TemperatureRace, r.WeatherStateRace, ? AS WeekendType FROM Races r WHERE r.SeasonID = 2024 AND r.TrackID = ? LIMIT 1 `, [newRaceId, Day, WeekendType, TrackID], 'run'); newRaceId++; } queryDB(`CREATE TRIGGER IF NOT EXISTS delete_duplicate_2025 AFTER INSERT ON Races WHEN NEW.SeasonID = 2025 AND EXISTS ( SELECT 1 FROM Races WHERE SeasonID = 2025 AND TrackID = NEW.TrackID AND RaceID <> NEW.RaceID ) BEGIN DELETE FROM Races WHERE RaceID = NEW.RaceID; END;`, [], 'run'); updateSeasonModTable("change-calendar", 1, "2025"); } else if (type === "Direct2025") { let maxRaceId = queryDB(`SELECT MAX(RaceID) FROM Races`, [], "singleRow")[0]; let newRaceId = maxRaceId + 1; let firstNewRaceID = newRaceId; for (const entry of changes.Calendar) { const { TrackID, Day, WeekendType } = entry; queryDB(` INSERT INTO Races ( RaceID, SeasonID, TrackID, Day, State, RainPractice, TemperaturePractice, WeatherStatePractice, RainQualifying, TemperatureQualifying, WeatherStateQualifying, RainRace, TemperatureRace, WeatherStateRace, WeekendType ) SELECT ? AS RaceID, 2025 AS SeasonID, r.TrackID, ? AS Day, 0 AS State, r.RainPractice, r.TemperaturePractice, r.WeatherStatePractice, r.RainQualifying, r.TemperatureQualifying, r.WeatherStateQualifying, r.RainRace, r.TemperatureRace, r.WeatherStateRace, ? AS WeekendType FROM Races r WHERE r.SeasonID = 2025 AND r.TrackID = ? LIMIT 1 `, [newRaceId, Day, WeekendType, TrackID], 'run'); newRaceId++; } // Borra las filas antiguas de la temporada 2025 queryDB(` DELETE FROM Races WHERE SeasonID = 2025 AND RaceID < ? `, [firstNewRaceID], 'run'); } } } export function updateCalendar2026(type) { if (!changes2026.Calendar || !Array.isArray(changes2026.Calendar)) { console.log("No calendar data found"); } else { if (type === "Start2024" || type === "End2024" || type === "Start2025" || type === "End2025") { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], "singleRow"); const season = daySeason[1]; let maxRaceId = queryDB(`SELECT MAX(RaceID) FROM Races`, [], "singleRow")[0]; let newRaceId = maxRaceId + 1; for (const entry of changes2026.Calendar) { const { TrackID, Day, WeekendType, deleteIfGameAdds, isF2Weekend } = entry; if (TrackID === null) { continue; } //if it's either true or false, but not undefinded or null, then if (isF2Weekend !== null && isF2Weekend !== undefined) { if (isF2Weekend) { queryDB(`UPDATE Races_Tracks SET IsF2Race = 1 WHERE TrackID = ?`, [TrackID], 'run'); } else { queryDB(`UPDATE Races_Tracks SET IsF2Race = 0 WHERE TrackID = ?`, [TrackID], 'run'); } } //if deleteIfGameAdds is true, skip the manual insert and let the trigger remove it if the game adds it if (!deleteIfGameAdds) { queryDB(` INSERT INTO Races ( RaceID, SeasonID, TrackID, Day, State, RainPractice, TemperaturePractice, WeatherStatePractice, RainQualifying, TemperatureQualifying, WeatherStateQualifying, RainRace, TemperatureRace, WeatherStateRace, WeekendType ) SELECT ? AS RaceID, 2026 AS SeasonID, r.TrackID, ? AS Day, 0 AS State, r.RainPractice, r.TemperaturePractice, r.WeatherStatePractice, r.RainQualifying, r.TemperatureQualifying, r.WeatherStateQualifying, r.RainRace, r.TemperatureRace, r.WeatherStateRace, ? AS WeekendType FROM Races r WHERE r.SeasonID = ? AND r.TrackID = ? LIMIT 1 `, [newRaceId, Day, WeekendType, season, TrackID], 'run'); newRaceId++; } } queryDB(`DROP TRIGGER IF EXISTS delete_duplicate_2026`, [], 'run'); queryDB(`CREATE TRIGGER delete_duplicate_2026 AFTER INSERT ON Races WHEN NEW.SeasonID = 2026 AND ( NEW.TrackID IN (2, 11, 24) OR EXISTS ( SELECT 1 FROM Races WHERE SeasonID = 2026 AND TrackID = NEW.TrackID AND RaceID <> NEW.RaceID ) ) BEGIN DELETE FROM Races WHERE RaceID = NEW.RaceID; END;`, [], 'run'); updateSeasonModTable("change-calendar-2026", 1, "2026"); } else if (type === "Direct2026") { let maxRaceId = queryDB(`SELECT MAX(RaceID) FROM Races`, [], "singleRow")[0]; let newRaceId = maxRaceId + 1; let firstNewRaceID = newRaceId; for (const entry of changes.Calendar) { const { TrackID, Day, WeekendType } = entry; queryDB(` INSERT INTO Races ( RaceID, SeasonID, TrackID, Day, State, RainPractice, TemperaturePractice, WeatherStatePractice, RainQualifying, TemperatureQualifying, WeatherStateQualifying, RainRace, TemperatureRace, WeatherStateRace, WeekendType ) SELECT ? AS RaceID, 2026 AS SeasonID, r.TrackID, ? AS Day, 0 AS State, r.RainPractice, r.TemperaturePractice, r.WeatherStatePractice, r.RainQualifying, r.TemperatureQualifying, r.WeatherStateQualifying, r.RainRace, r.TemperatureRace, r.WeatherStateRace, ? AS WeekendType FROM Races r WHERE r.SeasonID = 2026 AND r.TrackID = ? LIMIT 1 `, [newRaceId, Day, WeekendType, TrackID], 'run'); newRaceId++; } // Borra las filas antiguas de la temporada 2026 queryDB(` DELETE FROM Races WHERE SeasonID = 2026 AND RaceID < ? `, [firstNewRaceID], 'run'); updateSeasonModTable("change-calendar-2026", 1, "2026"); } } } export function insertStaff2025() { let tables = ["Staff_BasicData", "Staff_PerformanceStats", "Staff_State", "Staff_DriverData", "Staff_GameData"]; tables.forEach((table) => { if (changes[table] && Array.isArray(changes[table])) { changes[table].forEach((entry) => { let columns = Object.keys(entry).join(", "); let values = Object.values(entry); // Generate placeholders for values let placeholders = values.map(() => "?").join(", "); // Filter null values for SQL let sqlValues = values.map(value => value === null ? null : value); // Table names cannot be parameterized, but values can queryDB(`INSERT INTO ${table} (${columns}) VALUES (${placeholders})`, sqlValues, 'run'); }); } }); changeBudgets(); updateSeasonModTable("extra-drivers", 1, "2025"); } function changeBudgets(amount = 15000000) { queryDB(`UPDATE Finance_TeamBalance SET Balance = Balance + ?`, [amount], 'run'); } export function removeFastestLap(mod = "2025") { queryDB(`UPDATE Regulations_Enum_Changes SET CurrentValue = 0, PreviousValue = 1 WHERE ChangeID = 9`, [], 'run'); if (mod === "2025") { updateSeasonModTable("change-regulations", 1, "2025"); } } function updateSeasonModTable(edit, value, mod = "2025") { const table = mod === "2026" ? "Custom_2026_SeasonMod" : "Custom_2025_SeasonMod"; queryDB(`INSERT OR REPLACE INTO ${table} (key, value) VALUES (?, ?)`, [edit, value], 'run'); } export function updatePerofmrnace2025() { const globals = getGlobals(); const teamDict = getBestParts(globals.isCreateATeam); let tyreDegDict = {}; for (let team of Object.keys(teamDict).filter(key => key !== "0")) { //remove the part 0 from teamDict[team] delete teamDict[team]["0"]; let teamboost = changes.Performance.find(x => x.TeamID === Number(team)); applyBoostToCarStats(teamDict[team], teamboost.Boost, teamboost.TeamID); const tyreDegStatsTemas = getTyreDegStats(teamDict[team]); tyreDegDict[team] = tyreDegStatsTemas; } for (let team of Object.keys(teamDict).filter(key => key !== "0")) { let teamGivingTyreDeg = changes.Performance.find(x => x.TeamID === Number(team)).TyreDeg; let tyreDegStats = tyreDegDict[teamGivingTyreDeg]; updateTyreDegStats(teamDict[team], tyreDegStats, team, teamGivingTyreDeg); } updateSeasonModTable("change-performance", 1, "2025"); } export function fixes_mod() { let error = false; const extraDrivers = queryDB(`SELECT value FROM Custom_2025_SeasonMod WHERE key = 'extra-drivers'`, [], "singleValue"); if (extraDrivers === "1") { if (!changes.Fixes || !Array.isArray(changes.Fixes)) { console.log("No fixes found"); } else { for (const fix of changes.Fixes) { const { StaffID, Table, Column, Value } = fix; // console.log(`SELECT ${Column} FROM ${Table} WHERE StaffID = ${StaffID}`); // Column and Table cannot be parameterized const value = queryDB(`SELECT ${Column} FROM ${Table} WHERE StaffID = ?`, [StaffID], "singleValue"); if (value !== undefined && value !== Value) { queryDB(`UPDATE ${Table} SET ${Column} = ? WHERE StaffID = ?`, [Value, StaffID], 'run'); error = true; } } } } return error; } export function updateEditsWithModData(data) { const selectorAliases = { "change-cfd-2026": [".add-results-2026"] }; for (let key in data) { if (data[key] === "1") { const selectors = [`.${key}`, ...(selectorAliases[key] || [])]; selectors.forEach((sel) => { const elem = document.querySelector(sel); if (!elem) return; elem.classList.add("completed") elem.classList.remove("disabled") const span = elem.querySelector("span"); if (span) span.textContent = "Applied" }); } } } export function updateRenaultToHonda(isHonda) { const newName = isHonda ? 'Honda' : 'Renault'; queryDB(`UPDATE Custom_Engines_List SET name = ? WHERE engineId = 10`, [newName], 'run'); setCustomSaveConfig('renaultEngine', isHonda ? 'honda' : 'renault'); } export function addAudiCustomEngine(unitValue = 80) { const [customEngines] = fetchEngines(); const normalizedName = (val) => String(val || "").trim().toLowerCase(); let audiEngineId = null; const existingAudi = customEngines.find((engine) => normalizedName(engine?.[2]) === "audi"); if (existingAudi) { audiEngineId = existingAudi[0]; } else { const maxEngineId = customEngines.reduce((max, engine) => { const id = Number(engine?.[0]); if (!Number.isFinite(id)) return max; return Math.max(max, id); }, 0); audiEngineId = maxEngineId ? (maxEngineId + 3) : 14; } updateCustomEngines({ [audiEngineId]: { name: "audi", stats: { 6: unitValue, 10: unitValue, 11: unitValue, 12: unitValue, 14: unitValue, 18: unitValue, 19: unitValue } } }); return audiEngineId; } export function apply2026EnginePerformanceChanges() { updateRenaultToHonda(true); let audiEngineId = null; try { audiEngineId = addAudiCustomEngine(80); } catch (e) { console.warn("Failed to add custom Audi engine:", e); } try { manage_engine_change(10, 10); if (audiEngineId != null) { manage_engine_change(9, audiEngineId); } manage_engine_change(5, 7); } catch (e) { console.warn("Failed to update engine allocations:", e); } const enginePerformance = changes2026?.EnginePerformance || {}; const engineStatsById = { 1: enginePerformance.ferrari, 4: enginePerformance.rbpt, 7: enginePerformance.mercedes, 10: enginePerformance.honda }; if (audiEngineId != null) { engineStatsById[audiEngineId] = enginePerformance.audi; } const enginesToEdit = Object.fromEntries( Object.entries(engineStatsById).filter(([, stats]) => !!stats) ); if (Object.keys(enginesToEdit).length > 0) { editEngines(enginesToEdit); } } export function insertStaff2026() { let tables = ["Staff_BasicData", "Staff_State", "Staff_DriverData", "Staff_GameData"]; tables.forEach((table) => { if (tables2026[table] && Array.isArray(tables2026[table])) { tables2026[table].forEach((entry) => { const entryKeys = Object.keys(entry); let columns = entryKeys.join(", "); let values = Object.values(entry); let placeholders = values.map(() => "?").join(", "); let sqlValues = [...values]; let primaryKeyColumn = "StaffID"; let staffID = entry[primaryKeyColumn]; const existingEntry = queryDB( `SELECT 1 FROM ${table} WHERE ${primaryKeyColumn} = ?`, [staffID], "singleRow" ); if (!existingEntry || staffIDChanges[staffID]) { if (staffIDChanges[staffID]) { const pkIndex = entryKeys.findIndex(key => key === primaryKeyColumn); sqlValues[pkIndex] = staffIDChanges[staffID]; } // En Staff_DriverData, forzar estos campos a NULL en INSERT if (table === "Staff_DriverData") { const assignedCarIndex = entryKeys.findIndex(key => key === "AssignedCarNumber"); const feederAssignedCarIndex = entryKeys.findIndex(key => key === "FeederSeriesAssignedCarNumber"); if (assignedCarIndex !== -1) { sqlValues[assignedCarIndex] = null; } if (feederAssignedCarIndex !== -1) { sqlValues[feederAssignedCarIndex] = null; } } queryDB( `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`, sqlValues, "run" ); } else { let isGeneratedForCustomTeam = queryDB( `SELECT IsGeneratedForCustomTeam FROM Staff_BasicData WHERE StaffID = ?`, [staffID], "singleValue" ); if (isGeneratedForCustomTeam !== 1) { // En UPDATE, excluir estas columnas de Staff_DriverData let keysToUpdate = entryKeys.filter(key => key !== primaryKeyColumn); if (table === "Staff_DriverData") { keysToUpdate = keysToUpdate.filter( key => key !== "AssignedCarNumber" && key !== "FeederSeriesAssignedCarNumber" ); } let setClause = keysToUpdate.map(key => `${key} = ?`).join(", "); let updateValues = keysToUpdate.map(key => entry[key]); updateValues.push(entry[primaryKeyColumn]); queryDB( `UPDATE ${table} SET ${setClause} WHERE ${primaryKeyColumn} = ?`, updateValues, "run" ); } else { let staffID = entry[primaryKeyColumn]; staffIDChanges[staffID] = newStaffIDCounter; const pkIndex = entryKeys.findIndex(key => key === primaryKeyColumn); sqlValues[pkIndex] = staffIDChanges[staffID]; // También aquí, si es Staff_DriverData, forzar NULL en INSERT if (table === "Staff_DriverData") { const assignedCarIndex = entryKeys.findIndex(key => key === "AssignedCarNumber"); const feederAssignedCarIndex = entryKeys.findIndex(key => key === "FeederSeriesAssignedCarNumber"); if (assignedCarIndex !== -1) { sqlValues[assignedCarIndex] = null; } if (feederAssignedCarIndex !== -1) { sqlValues[feederAssignedCarIndex] = null; } } queryDB( `INSERT INTO ${table} (${columns}) VALUES (${placeholders})`, sqlValues, "run" ); newStaffIDCounter++; } } }); } }); updateSeasonModTable("extra-drivers-2026", 1, "2026"); } export function changeStats2026() { if (!tables2026.Staff_PerformanceStats || !Array.isArray(tables2026.Staff_PerformanceStats)) { console.log("No stats found"); } else { tables2026.Staff_PerformanceStats.forEach((entry) => { let staffID = entry.StaffID; if (staffIDChanges[staffID]) { staffID = staffIDChanges[staffID]; } //check if the driver already has performance stats let existingStats = queryDB(`SELECT 1 FROM Staff_PerformanceStats WHERE StaffID = ? AND StatID = ?`, [staffID, entry.StatID], "singleRow"); if (existingStats) { //update where staffid = entry.staffid and statid = entry.statid let setClause = Object.keys(entry).filter(key => key !== "StaffID" && key !== "StatID").map(key => `${key} = ?`).join(", "); let values = Object.keys(entry).filter(key => key !== "StaffID" && key !== "StatID").map(key => entry[key]); //if all of the values[1] are 0, skip if (values.slice(1).every(v => v === 0)) { // console.log("Skipping update for StaffID:", entry.StaffID, "StatID:", entry.StatID); return; } values.push(staffID); // Add StaffID at the end for the WHERE clause values.push(entry.StatID); // Add StatID at the end for the WHERE clause queryDB(`UPDATE Staff_PerformanceStats SET ${setClause} WHERE StaffID = ? AND StatID = ?`, values, 'run'); } else { //check if the staff ID exists in staff_basicData, if not, skip const staffExists = queryDB(`SELECT 1 FROM Staff_BasicData WHERE StaffID = ?`, [staffID], "singleRow"); if (!staffExists) { console.log("StaffID:", staffID, "does not exist in Staff_BasicData. Skipping performance stats insertion."); return; } let columns = Object.keys(entry).join(", "); let values = Object.values(entry); // Generate placeholders for values let placeholders = values.map(() => "?").join(", "); // Filter null values for SQL let sqlValues = values.map(value => value === null ? null : value); //change the staffID in sqlValues if it is in staffIDChanges if (staffIDChanges[entry.StaffID]) { sqlValues[sqlValues.findIndex((_, index) => Object.keys(entry)[index] === "StaffID")] = staffIDChanges[entry.StaffID]; } queryDB(`INSERT INTO Staff_PerformanceStats (${columns}) VALUES (${placeholders})`, sqlValues, 'run'); } }); updateSeasonModTable("change-stats-2026", 1, "2026"); } if (!tables2026.Staff_Performancestats_StartOfMonth || !Array.isArray(tables2026.Staff_Performancestats_StartOfMonth)) { console.log("No performance stats start of month data found"); } else { tables2026.Staff_Performancestats_StartOfMonth.forEach((entry) => { const { StaffID, StatID, Val } = entry; let staffID = StaffID; if (staffIDChanges[staffID]) { staffID = staffIDChanges[staffID]; } const existingStart = queryDB( `SELECT 1 FROM Staff_Performancestats_StartOfMonth WHERE StaffID = ? AND StatID = ?`, [staffID, StatID], "singleRow" ); if (existingStart) { queryDB( `UPDATE Staff_Performancestats_StartOfMonth SET Val = ? WHERE StaffID = ? AND StatID = ?`, [Val, staffID, StatID], "run" ); } else { queryDB( `INSERT INTO Staff_Performancestats_StartOfMonth (StaffID, StatID, Val) VALUES (?, ?, ?)`, [staffID, StatID, Val], "run" ); } }); } console.log("staffIDChanges at the end:", staffIDChanges); } export function changeLineUps2026() { if (!tables2026.Staff_Contracts || !Array.isArray(tables2026.Staff_Contracts)) { console.log("No contracts found"); } else { wipeTableAndRefill("Staff_Contracts", tables2026.Staff_Contracts); updateSeasonModTable("change-line-ups-2026", 1, "2026"); const globals = getGlobals(); if (!globals.isCreateATeam) { queryDB(`DELETE FROM Staff_Contracts WHERE TeamID = 32`, [], 'run'); } } if (!tables2026.Staff_RaceEngineerDriverAssignments || !Array.isArray(tables2026.Staff_RaceEngineerDriverAssignments)) { console.log("No race engineer driver assignments found"); } else { wipeTableAndRefill("Staff_RaceEngineerDriverAssignments", tables2026.Staff_RaceEngineerDriverAssignments); } if (!tables2026.Staff_NarrativeData || !Array.isArray(tables2026.Staff_NarrativeData)) { console.log("No narrative data found"); } else { queryDB( `UPDATE Staff_NarrativeData SET IsActive = 0 WHERE TeamID BETWEEN 1 AND 10 AND JobTitle = '[STAFF_TYPE_5]'`, [], "run" ); tables2026.Staff_NarrativeData.forEach((entry) => { const { StaffID, GenSource, JobTitle, TeamID, IsActive } = entry; let staffID = StaffID; if (staffIDChanges[staffID]) { staffID = staffIDChanges[staffID]; } const existingNarrative = queryDB( `SELECT 1 FROM Staff_NarrativeData WHERE StaffID = ?`, [staffID], "singleRow" ); if (existingNarrative) { queryDB( `UPDATE Staff_NarrativeData SET GenSource = ?, JobTitle = ?, TeamID = ?, IsActive = ? WHERE StaffID = ?`, [GenSource, JobTitle, TeamID, IsActive, staffID], "run" ); } else { queryDB( `INSERT INTO Staff_NarrativeData (StaffID, GenSource, JobTitle, TeamID, IsActive) VALUES (?, ?, ?, ?, ?)`, [staffID, GenSource, JobTitle, TeamID, IsActive], "run" ); } }); queryDB( `DELETE FROM Staff_NarrativeData WHERE TeamID BETWEEN 1 AND 10 AND JobTitle = '[STAFF_TYPE_5]' AND IsActive = 0`, [], "run" ); queryDB(`DELETE FROM Staff_NarrativeData WHERE StaffID = ?`, [396], "run"); queryDB(`DELETE FROM Staff_BasicData WHERE StaffID = ?`, [396], "run"); queryDB(`DELETE FROM Staff_GameData WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_State WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_PerformanceStats WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_Performancestats_StartOfMonth WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_ContractPatience WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_Mentality_AreaOpinions WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_Mentality_Statuses WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); queryDB(`DELETE FROM Staff_RaceRecord WHERE StaffID IN (290, 291, 295, 396)`, [], "run"); } //get all StaffID from Staff_DriverData const driverIDs = queryDB(`SELECT StaffID FROM Staff_DriverData`, [], "allRows") //for each driver, get its AssignedCarNumber and FeederSeriesAssignedCarNumber from tables2026.Staff_DriverData and update the corresponding driver in the database with those values driverIDs.forEach((driverID) => { const driverData = tables2026.Staff_DriverData.find(driver => driver.StaffID === driverID[0]); if (driverData) { const { AssignedCarNumber, FeederSeriesAssignedCarNumber } = driverData; queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ?, FeederSeriesAssignedCarNumber = ? WHERE StaffID = ?`, [AssignedCarNumber, FeederSeriesAssignedCarNumber, driverID[0]], 'run'); } }); // fixStandings("2025"); } export function changeDriverNumbers2026() { if (!tables2026.Staff_DriverNumbers || !Array.isArray(tables2026.Staff_DriverNumbers)) { console.log("No driver numbers found"); } else { tables2026.Staff_DriverNumbers.forEach((entry) => { const { CurrentHolder, Number } = entry; let driverExists = queryDB(`SELECT 1 FROM Staff_DriverData WHERE StaffID = ?`, [CurrentHolder], "singleRow"); if (driverExists) { queryDB(`UPDATE Staff_DriverNumbers SET CurrentHolder = ? WHERE Number = ?`, [CurrentHolder, Number], 'run'); } else if (CurrentHolder !== null) { console.log("Driver with StaffID:", CurrentHolder, "does not exist. Skipping driver number update for number:", Number); } }); } } export function updatePerofmrnace2026() { const globals = getGlobals(); const customTeam = globals.isCreateATeam; const perf = getPerformanceAllTeams(null, null, customTeam); const ovr32 = perf[32]; const teamDict = getBestParts(customTeam); let tyreDegDict = {}; for (let team of Object.keys(teamDict)) { delete teamDict[team]["0"]; const teamId = Number(team); let teamboost = changes2026.Performance.find(x => x.TeamID === teamId); if (teamId === 32) { const objective = teamboost?.Objective ?? 23.4; const dynamicBoost = (typeof ovr32 === "number" && ovr32 > 0) ? (objective / ovr32) : (teamboost?.Boost ?? 1); applyBoostToCarStats(teamDict[team], dynamicBoost, teamId); } else { applyBoostToCarStats(teamDict[team], teamboost.Boost, teamId); } const tyreDegStatsTemas = getTyreDegStats(teamDict[team]); tyreDegDict[team] = tyreDegStatsTemas; } for (let team of Object.keys(teamDict)) { let teamGivingTyreDeg = changes2026.Performance.find(x => x.TeamID === Number(team)).TyreDeg; let tyreDegStats = tyreDegDict[teamGivingTyreDeg]; updateTyreDegStats(teamDict[team], tyreDegStats, team, teamGivingTyreDeg); } for (let team of Object.keys(teamDict)) { if (changes2026.Performance.find(x => x.TeamID === Number(team))?.Expertise) { let expertiseBoost = changes2026.Performance.find(x => x.TeamID === Number(team)).Expertise; applyExpertiseBoost(expertiseBoost, team); } } updateSeasonModTable("change-performance-2026", 1, "2026"); } export function fixStandings(forSeason = "2025") { //driverids that are in staff_driverdata and have a contract with teamids between 0 and 10 or 32 and posInTeam <= 2 const f1Drivers = queryDB(`SELECT StaffID FROM Staff_Contracts WHERE (TeamID <= 10 OR TeamID = 32) AND PosInTeam <= 2 AND StaffID IN (SELECT StaffID FROM Staff_DriverData)`, [], "allRows"); //delete all from that season queryDB(`DELETE FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = 1`, [forSeason], 'run'); let position = 1; f1Drivers.forEach((driver) => { queryDB(`INSERT INTO Races_DriverStandings (SeasonID, DriverID, Points, Position, LastPointsChange, LastPositionChange, RaceFormula) VALUES (?, ?, 0, ?, 0, 0, 1)`, [forSeason, driver[0], position], 'run'); position++; }); } export function changeAdditionalRegulations2026() { if (!changes2026.Regulations || !Array.isArray(changes2026.Regulations)) { console.log("No regulations changes found"); } else { changes2026.Regulations.forEach((reg) => { const { Name, CurrentValue, MinValue, MaxValue } = reg; queryDB(`UPDATE Regulations_Enum_Changes SET CurrentValue = ?, MinValue = ?, MaxValue = ? WHERE Name = ?`, [CurrentValue, MinValue, MaxValue, Name], 'run'); }); changeBudgets(20000000); updateFacilities2026(); updateSeasonModTable("change-regulations-2026", 1, "2026"); } } export function change2025Standings(mod = "2026") { if (!changes2026.DriverStandings || !Array.isArray(changes2026.DriverStandings)) { console.error("No driver standings found"); } else { for (const entry of changes2026.DriverStandings) { const { DriverID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID } = entry; const existingEntry = queryDB(`SELECT * FROM Races_DriverStandings WHERE DriverID = ? AND RaceFormula = ? AND SeasonID = ?`, [DriverID, RaceFormula, SeasonID], "singleRow"); if (!existingEntry) { queryDB(`INSERT INTO Races_DriverStandings (DriverID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID) VALUES (?, ?, ?, ?, ?, ?, ?)`, [DriverID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID], 'run'); console.log("Inserted new driver standing for DriverID:", DriverID); } else { queryDB(` UPDATE Races_DriverStandings SET LastPointsChange = ?, LastPositionChange = ?, Points = ?, Position = ? WHERE DriverID = ? AND RaceFormula = ? AND SeasonID = ? `, [LastPointsChange, LastPositionChange, Points, Position, DriverID, RaceFormula, SeasonID], 'run'); } } } if (!changes2026.TeamStandings || !Array.isArray(changes2026.TeamStandings)) { console.error("No team standings found"); } else { queryDB(`DELETE FROM Races_TeamStandings WHERE RaceFormula = 1 AND SeasonID = 2025`, [], 'run'); for (const entry of changes2026.TeamStandings) { const { LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID, TeamID } = entry; queryDB(` INSERT INTO Races_TeamStandings (TeamID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID) VALUES (?, ?, ?, ?, ?, ?, ?) `, [TeamID, LastPointsChange, LastPositionChange, Points, Position, RaceFormula, SeasonID], 'run'); } const globals = getGlobals(); if (!globals.isCreateATeam) { queryDB(`DELETE FROM Races_TeamStandings WHERE TeamID = 32`, [], 'run'); } } updateRecordsTo2026(); if (mod === "2026") { updateSeasonModTable("change-cfd-2026", 1, "2026"); } } export function fixesMod2026() { //if has trhe extra drivers let wasError = false; let badStandings = false; const extraDrivers = queryDB(`SELECT value FROM Custom_2026_SeasonMod WHERE key = 'extra-drivers-2026'`, [], "singleValue"); const isWrong = queryDB(`SELECT Gender FROM Staff_BasicData WHERE StaffID = 654`, [], "singleValue"); if (extraDrivers === "1" && isWrong === 0) { queryDB(`UPDATE Staff_BasicData SET Gender = 1 WHERE StaffID = 654`, [], "run") wasError = true; } let errorDict = { "standings": badStandings, "newStandings": null, "extraDrivers": isWrong === 0, "generalWasError": wasError }; return errorDict; } function updateRecordsTo2026() { if (!changes2026.Records || !Array.isArray(changes2026.Records)) { console.log("No records changes found"); } else { changes2026.Records.forEach((record) => { const { StaffID, Wins, Poles, Podiums, FastestLaps, Championships } = record; const points2025 = changes2026.DriverStandings.find(x => x.DriverID === StaffID)?.Points || 0; const points2024 = changes.DriverStandings.find(x => x.DriverID === StaffID)?.Points || 0; const points = points2025 - points2024; queryDB(`UPDATE Staff_Driver_RaceRecordBeforeGameStart SET TotalWins = TotalWins + ?, TotalPoles = TotalPoles + ?, TotalPodiums = TotalPodiums + ?, TotalFastestLaps = TotalFastestLaps + ?, TotalChampionshipWins = TotalChampionshipWins + ?, TotalPointsScored = TotalPointsScored + ? WHERE StaffID = ?`, [Wins, Poles, Podiums, FastestLaps, Championships, points, StaffID], 'run'); }); } } function updateFacilities2026() { if (!changes2026.Facilities || !Array.isArray(changes2026.Facilities)) { console.log("No teams HQ changes found"); } else { changes2026.Facilities.forEach((fac) => { const { TeamID, UpgradeBy } = fac; //add one if it doesn't en in 5 queryDB(` UPDATE Buildings_HQ SET BuildingID = BuildingID + CASE WHEN (BuildingID % 10) = 5 THEN 0 ELSE ? END WHERE TeamID = ? `, [UpgradeBy, TeamID], "run"); }); } } ================================================ FILE: src/js/backend/scriptUtils/newsUtils.js ================================================ import { fetchEventsDoneFrom, formatNamesSimple, fetchEventsDoneBefore, fetchPointsRegulations, computeDriverOfTheDayFromRows, getDoDTopNForRace, editEngines, fetchEngines, ensureCustomEngineProgressionTable, snapshotEnginePowerProgression } from "./dbUtils"; import { races_names, countries_dict, countries_data, getParamMap, team_dict, combined_dict, opinionDict, part_full_names, continentDict, contintntRacesRegions, defaultTurningPointsFrequencyPreset, turningPointsTuningByType } from "../../frontend/config"; import newsTitleTemplates from "../../../data/news/news_titles_templates.json"; import turningPointsTitleTemplates from "../../../data/news/turning_points_titles_templates.json"; import { fetchSeasonResults, fetchQualiResults } from "./dbUtils"; import { queryDB } from "../dbManager"; import { excelToDate, dateToExcel, driverStats } from "./eidtStatsUtils"; import { getTier, getDriverOverall, fireDriver, hireDriver, swapDrivers } from "./transferUtils"; import { getPerformanceAllTeamsSeason, getAllPartsFromTeam, getPerformanceAllTeams } from "./carAnalysisUtils"; import { getGlobals } from "../commandGlobals"; import { unitValueToValue } from "./carConstants"; import { track } from "@vercel/analytics"; import LZString from "lz-string"; import { enrichDriversWithHistory, fetchDriverHistoryRecords } from "./recordUtils"; const USE_COMPRESSION = false; const _seasonResultsCache = new Map(); export const _standingsCache = new Map(); const _dropsCache = new Map(); function isTimeTravel2026Enabled() { try { const exists = queryDB( `SELECT name FROM sqlite_master WHERE type='table' AND name='Custom_2026_SeasonMod'`, [], 'singleRow' ); if (!exists) return false; const value = queryDB( `SELECT value FROM Custom_2026_SeasonMod WHERE key = 'time-travel-2026'`, [], 'singleValue' ); return value === "1" || value === 1; } catch { return false; } } function loadTurningPointsFrequencyConfig() { try { const presetRaw = queryDB( `SELECT value FROM Custom_Save_Config WHERE key = 'turningPointsFrequencyPreset'`, [], 'singleValue' ); let presetIndex = defaultTurningPointsFrequencyPreset; if (presetRaw !== null && presetRaw !== undefined) { presetIndex = parseInt(presetRaw, 10); } return { presetIndex }; } catch { return { presetIndex: defaultTurningPointsFrequencyPreset }; } } function getTurningPointChance(tpType, tpConfig) { let presetIndex = tpConfig?.presetIndex; if (presetIndex === undefined || presetIndex === null) { presetIndex = defaultTurningPointsFrequencyPreset; } return turningPointsTuningByType[tpType].chance[presetIndex]; } function getTurningPointMax(tpType, tpConfig) { let presetIndex = tpConfig?.presetIndex; if (presetIndex === undefined || presetIndex === null) { presetIndex = defaultTurningPointsFrequencyPreset; } return turningPointsTuningByType[tpType].max[presetIndex]; } function fetchSeasonResultsCached(season) { if (_seasonResultsCache.has(season)) return _seasonResultsCache.get(season); const res = fetchSeasonResults(season); _seasonResultsCache.set(season, res); return res; } export function rebuildStandingsUntilCached(season, seasonResults, raceId, includeCurrentRacePrevResults = false, includeCurrentRacePoints = true) { const key = `${season}:${raceId}:${includeCurrentRacePrevResults}:${includeCurrentRacePoints}`; if (_standingsCache.has(key)) return _standingsCache.get(key); const res = rebuildStandingsUntil(seasonResults, raceId, includeCurrentRacePrevResults, includeCurrentRacePoints); _standingsCache.set(key, res); return res; } export function generate_news(savednews, turningPointState) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); if (isTimeTravel2026Enabled() && Number(daySeason?.[1]) < 2026) { const existingList = Object.entries(savednews || {}).map(([id, n]) => ({ id, ...n })); existingList.sort((a, b) => (Number(b.date) || 0) - (Number(a.date) || 0)); return { newsList: existingList, turningPointState }; } const racesDone = fetchEventsDoneFrom(daySeason[1]); const tpConfig = loadTurningPointsFrequencyConfig(); // const potentialChampionTestRaceId = 216; // Set to null for normal operation. const potentialChampionNewsList = generateChampionMilestones(racesDone, savednews); const currentDate = excelToDate(daySeason[0]); const currentMonth = currentDate.getMonth() + 1; const rumorMonths = [4, 5, 6, 7]; const comparisonMonths = [4, 5, 6, 7, 8, 9, 10]; const monthsDone = rumorMonths.filter(m => m <= currentMonth); const raceNews = generateRaceResultsNews(racesDone, savednews); const qualiNews = generateQualifyingResultsNews(racesDone, savednews); const raceReactions = generateRaceReactionsNews(racesDone, savednews); const comparisonNews = generateComparisonNews(comparisonMonths, savednews); const transferRumors = getTrueTransferRumors(); const sillySeasonNews = generateTransferRumorsNews(transferRumors, savednews); const contractRenewals = getContractExtensions(); const bigConfirmedTransfersNews = generateBigConfirmedTransferNews(savednews, currentMonth); const fakeTransferNews = generateFakeTransferNews(monthsDone, savednews, bigConfirmedTransfersNews); const contractRenewalsNews = generateContractRenewalsNews(savednews, contractRenewals, currentMonth); const seasonReviews = generateSeasonReviewNews(savednews); const nextSeasonGridNews = generateNextSeasonGridNews(savednews, currentMonth); const juniorSeasonReviewNews = generateF2AndF3ReviewNews(currentMonth, savednews); const dsqTurningPointNews = generateDSQTurningPointNews(racesDone, savednews, turningPointState, tpConfig); const midSeasonTransfersTurningPointNews = generateMidSeasonTransfersTurningPointNews(monthsDone, currentMonth, savednews, turningPointState, tpConfig); const technicalDirectiveTurningPointNews = generateTechnicalDirectiveTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); const investmentTurningPointNews = generateInvestmentTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); const raceSubstitutionTurningPointNews = generateRaceSubstitutionTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); const driverInjuryTurningPointNews = generateDriverInjuryTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); const enginesTurningPointNews = generateEnginesTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); const youngDriversTurningPointNews = generateYoungDriversTurningPointNews(currentMonth, savednews, turningPointState, tpConfig); let aduoTPsEnabled = queryDB(`SELECT value FROM Custom_Save_Config WHERE key = 'aduo_tp_enabled'`, [], 'singleValue'); aduoTPsEnabled = aduoTPsEnabled === "1" || aduoTPsEnabled === 1; const aduoTurningPointNews = generateAduoTurningPointsNews(currentMonth, savednews, turningPointState, tpConfig, aduoTPsEnabled); let turningPointOutcomes = []; if (Object.keys(savednews).length > 0) { turningPointOutcomes = Object.entries(savednews) .filter(([_, n]) => n.type && n.type.startsWith("turning_point_outcome")) .map(([id, n]) => ({ id, ...n })); } let newsList = [...raceNews || [], ...qualiNews || [], ...fakeTransferNews || [], ...bigConfirmedTransfersNews || [], ...contractRenewalsNews || [], ...comparisonNews || [], ...seasonReviews || [], ...potentialChampionNewsList || [], ...sillySeasonNews || [], ...juniorSeasonReviewNews || [], ...dsqTurningPointNews || [], ...midSeasonTransfersTurningPointNews || [], ...turningPointOutcomes || [], ...technicalDirectiveTurningPointNews || [], ...investmentTurningPointNews || [], ...raceSubstitutionTurningPointNews || [], ...driverInjuryTurningPointNews || [], ...raceReactions || [], ...nextSeasonGridNews || [], ...enginesTurningPointNews || [], ...youngDriversTurningPointNews || [], ...aduoTurningPointNews || []]; // Include saved news entries that are not produced by the current generation logic (e.g. custom-created entries). // Otherwise, those entries would exist in the DB but not appear in the current-season view. const seenIds = new Set(newsList.map(n => n?.id).filter(Boolean)); for (const [id, n] of Object.entries(savednews || {})) { if (!id || seenIds.has(id) || !n) continue; if (n.type && typeof n.type === "string" && n.type.startsWith("turning_point_outcome")) continue; newsList.push({ id, ...n }); seenIds.add(id); } //order by date descending newsList.sort((a, b) => b.date - a.date); upsertNews(newsList); upsertTurningPoints(turningPointState); return { newsList, turningPointState }; } export function generateTurningResponse(turningPointData, type, maxDate, outcome) { let newEntry; if (type === "turning_point_transfer") { if (outcome === "positive") { executeMidSeasonTransfer(turningPointData); } const entryId = `turning_point_outcome_transfer_${turningPointData.month}`; const title = generateTurningPointTitle(turningPointData, 101, outcome); const image = getImagePath(null, turningPointData.driver_in.id, "transfer"); newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_transfer" } } else if (type === "turning_point_dsq") { if (outcome === "positive") { const pointsReg = fetchPointsRegulations(); disqualifyTeamInRace({ raceId: turningPointData.race_id, teamId: turningPointData.teamId, queryDB, pointsReg }); } const entryId = `turning_point_outcome_dsq_${turningPointData.race_id}`; const title = generateTurningPointTitle(turningPointData, 103, outcome); const image = getImagePath(null, null, "dsq"); newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_dsq" } maxDate += 1; } else if (type === "turning_point_technical_directive") { if (outcome === "positive") { applyTechnicalDirectiveEffect(turningPointData); } const entryId = `turning_point_outcome_technical_directive_${turningPointData.month}`; const title = generateTurningPointTitle(turningPointData, 100, outcome); const image = getImagePath(null, turningPointData.componentId, "technical"); newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_technical_directive" } maxDate += 1; } else if (type === "turning_point_investment") { if (outcome === "positive") { //apply investment effects: add the money and improve key facilities applyInvestmentEffect(turningPointData); } const entryId = `turning_point_outcome_investment_${turningPointData.month}`; const title = generateTurningPointTitle(turningPointData, 102, outcome); const image = getImagePath(null, turningPointData.investmentId, "investment") || "null.png"; newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_investment" } maxDate += 1; } else if (type === "turning_point_race_substitution") { if (outcome === "positive") { applyRaceSubstitution(turningPointData); } const entryId = `turning_point_outcome_race_substitution_${turningPointData.month}`; const title = generateTurningPointTitle(turningPointData, 105, outcome); const code = races_names[Number(turningPointData.newRaceTrackId)].toLowerCase() const image = getImagePath(null, code, "race_substitution") || "null.png"; newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_race_substitution" } maxDate += 1; } else if (type === "turning_point_injury") { if (outcome === "positive") { applyDriverInjury(turningPointData); } const entryId = `turning_point_outcome_injury_${turningPointData.month}`; const title = generateTurningPointTitle(turningPointData, 106, outcome); const image = getImagePath(null, null, "injury") || "null.png"; newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_injury" } maxDate += 1; } else if (type === "turning_point_engine_regulation") { if (outcome === "positive") { editEngines(turningPointData.engineData); } const entryId = `turning_point_outcome_engine_regulation_${turningPointData.season}`; const title = generateTurningPointTitle(turningPointData, 107, outcome); const image = getImagePath(null, "engine", "engine") || "null.png"; newEntry = { id: entryId, title, image, data: turningPointData, date: maxDate + 1, turning_point_type: outcome, type: "turning_point_outcome_engine_regulation" } maxDate += 1; } else if (type === "turning_point_young_drivers") { if (outcome === "positive") { applyYoungDriversBoost(turningPointData); } } else if(type === "turning_point_aduo") { if (outcome === "positive") { applyAduoEffect(turningPointData); } } return newEntry; } function applyAduoEffect(turningPointData) { const engineImprovements = turningPointData?.engineImprovements || []; if (!engineImprovements.length) { return; } ensureCustomEngineProgressionTable(); const [enginesData] = fetchEngines(); const enginesById = {}; for (const engineRow of enginesData || []) { enginesById[String(engineRow[0])] = engineRow; } const readStat = (stats, statId) => { if (!stats) return null; const raw = stats[statId] !== undefined ? stats[statId] : stats[String(statId)]; if (raw === undefined || raw === null) return null; return Number(raw); }; snapshotEnginePowerProgression( (enginesData || []).map((row) => row?.[0]).filter((id) => id !== null && id !== undefined), 'pre_aduo_tp', turningPointData?.season ); const clamp = (n, min, max) => Math.max(min, Math.min(max, n)); const engineDataToEdit = {}; for (const engineChange of engineImprovements) { const engineId = String(engineChange.engineId); const engineRow = enginesById[engineId]; if (!engineRow) continue; const baseStats = engineRow[1] || {}; const improvements = engineChange.improvements || {}; const newStats = {}; for (const statId of Object.keys(baseStats)) { newStats[statId] = Number(baseStats[statId]) || 0; } for (const statId of Object.keys(improvements)) { if (improvements[statId] === undefined || improvements[statId] === null) continue; const pct = improvements[statId]; const current = newStats[statId]; if (current === undefined || current === null) continue; const next = (current * (100 + pct)) / 100; newStats[statId] = clamp(next, 0, 100); } engineDataToEdit[engineId] = newStats; } console.log("[Aduo TP] Applying engine improvements:", engineImprovements); editEngines(engineDataToEdit); } function applyRaceSubstitution(turningPointData) { const raceId = turningPointData.raceId; const newTrackId = turningPointData.newRaceTrackId; const newDay = turningPointData.newRaceDay; queryDB(`UPDATE Races SET TrackID = ?, Day = ? WHERE RaceID = ?`, [newTrackId, newDay, raceId], 'run'); } function applyInvestmentEffect(turningPointData) { const teamId = turningPointData.teamId; const investmentAmount = turningPointData.investmentAmount; const keyBuildings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 15]; const level4Buildings = [2, 3, 5, 6, 7, 8]; for (const buildingId of keyBuildings) { //buildingID is a number that first has the actual id and the last number represents its level if (level4Buildings.includes(buildingId)) { //put it to at least level4, if its already at 4, improve to 5 const current = queryDB(`SELECT BuildingID FROM Buildings_HQ WHERE TeamID = ? AND BuildingType = ?`, [teamId, buildingId], 'singleValue'); let newLevel = 4; if (current) { //get last digit const currentLevel = parseInt(current.toString().slice(-1), 10); const safeLevel = Math.min(5, Math.max(0, currentLevel)); if (safeLevel >= 4) { newLevel = 5; } const newBuildingId = parseInt(current.toString().slice(0, -1) + newLevel.toString(), 10); queryDB(`UPDATE Buildings_HQ SET BuildingID = ?, DegradationValue = 1 WHERE BuildingType = ? AND TeamID = ?`, [newBuildingId, buildingId, teamId], 'run'); } } else {//set to at least level 4 const current = queryDB(`SELECT BuildingID FROM Buildings_HQ WHERE TeamID = ? AND BuildingType = ?`, [teamId, buildingId], 'singleValue'); if (current) { const currentStr = current.toString(); const currentLevel = parseInt(currentStr.slice(-1), 10); const safeLevel = Math.min(5, Math.max(0, currentLevel)); const newLevel = safeLevel >= 5 ? 5 : 4; const newBuildingId = parseInt(currentStr.slice(0, -1) + newLevel.toString(), 10); queryDB(`UPDATE Buildings_HQ SET BuildingID = ?, DegradationValue = 1 WHERE BuildingType = ? AND TeamID = ?`, [newBuildingId, buildingId, teamId], 'run'); } } } //multiply investment amount by 1 million const moneyToAdd = investmentAmount * 1000000; queryDB(`UPDATE Finance_TeamBalance SET Balance = Balance + ? WHERE TeamID = ?`, [moneyToAdd, teamId], 'run'); } function executeMidSeasonTransfer(turningPointData) { const { teamId, driver_out, driver_in, driver_substitute } = turningPointData; // Red Bull special case: Swap drivers if (teamId === 3 && (combined_dict[8] === "Alpha Tauri" || combined_dict[8] === "Visa Cashapp RB") && driver_in.teamId === 8) { // Fire both drivers fireDriver(driver_out.id, driver_out.teamId); fireDriver(driver_in.id, driver_in.teamId); // Hire them in their new teams const existingDrivers = queryDB(`SELECT con.PosInTeam FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2`, [driver_out.teamId], 'allRows').map(r => r[0]); const posInTeamForDriverIn = existingDrivers.includes(1) ? 2 : 1; hireDriver("auto", driver_in.id, driver_out.teamId, posInTeamForDriverIn); const existingDriversSubTeam = queryDB(`SELECT con.PosInTeam FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2`, [driver_in.teamId], 'allRows').map(r => r[0]); const posInTeamForDriverOut = existingDriversSubTeam.includes(1) ? 2 : 1; hireDriver("auto", driver_out.id, driver_in.teamId, posInTeamForDriverOut); return; } // --- Standard Transfer --- // 1. Fire all involved drivers from their current teams fireDriver(driver_out.id, driver_out.teamId); if (driver_in.teamId) { fireDriver(driver_in.id, driver_in.teamId); } if (driver_substitute && driver_substitute.teamId) { fireDriver(driver_substitute.id, driver_substitute.teamId); } // 2. Hire drivers into their new teams // Hire driver_in to the main team (teamId) const existingDrivers = queryDB(`SELECT con.PosInTeam FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2`, [teamId], 'allRows').map(r => r[0]); const posInTeamForDriverIn = existingDrivers.includes(1) ? 2 : 1; hireDriver("auto", driver_in.id, teamId, posInTeamForDriverIn); // Hire driver_substitute to driver_in's original team (if applicable) if (driver_substitute && driver_in.teamId) { const existingDriversSubTeam = queryDB(`SELECT con.PosInTeam FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2`, [driver_in.teamId], 'allRows').map(r => r[0]); const posInTeamForSubstitute = existingDriversSubTeam.includes(1) ? 2 : 1; hireDriver("auto", driver_substitute.id, driver_in.teamId, posInTeamForSubstitute); } } function applyTechnicalDirectiveEffect(turningPointData) { //iterate through the object turningPointData.effectOnEachteam const componentId = turningPointData.componentId; for (const [teamIdStr, effect] of Object.entries(turningPointData.effectOnEachteam)) { const teamId = Number(teamIdStr); const performanceChange = Number(effect.performanceGainLoss); const designs = queryDB(`SELECT DesignID FROM Parts_Designs WHERE TeamID = ? AND PartType = ?`, [teamId, componentId], 'allRows'); for (const designRow of designs) { const designId = designRow[0]; const unitValues = queryDB(`SELECT UnitValue, PartStat FROM Parts_Designs_StatValues WHERE DesignID = ? AND PartStat != 15`, [designId], 'allRows'); for (const valueRow of unitValues) { const partStat = valueRow[1]; const currentUnitValue = Number(valueRow[0]); const newRelative = 100 + performanceChange; const newUnitValue = (currentUnitValue * newRelative) / 100; const newValue = unitValueToValue[partStat](newUnitValue); queryDB(`UPDATE Parts_Designs_StatValues SET Value = ?, UnitValue = ? WHERE DesignID = ? AND PartStat = ?`, [newValue, newUnitValue, designId, partStat], 'run'); } } //now the expertise const expertise = queryDB(`SELECT Expertise, PartStat FROM Parts_TeamExpertise WHERE TeamID = ? AND PartType = ? AND PartStat != 15`, [teamId, componentId], 'allRows'); for (const expRow of expertise) { const currentExpertise = Number(expRow[0]); const partStat = expRow[1]; const newRelative = 100 + performanceChange; const newExpertise = (currentExpertise * newRelative) / 100; queryDB(`UPDATE Parts_TeamExpertise SET Expertise = ? WHERE TeamID = ? AND PartType = ? AND PartStat = ?`, [newExpertise, teamId, componentId, partStat], 'run'); } } } function generateRaceSubstitutionTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const months = [4, 5, 6, 7, 8, 9, 10, 11]; let newsList = []; for (let month of months) { const entryId = `turning_point_race_substitution_${month}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); } } const maxPerSeason = Math.min(months.length, getTurningPointMax("raceSubstitution", tpConfig)); if (newsList.length >= maxPerSeason) { return newsList; } if (turningPointState.raceSubstitutionOpportunities[currentMonth] !== null) { return newsList; } const chance = getTurningPointChance("raceSubstitution", tpConfig); if (Math.random() > chance) { turningPointState.raceSubstitutionOpportunities[currentMonth] = "None"; return newsList; } //get races that are still to be done const calendar = queryDB(`SELECT RaceID, TrackID, Day FROM Races WHERE SeasonID = ? AND State = 0 ORDER BY Day`, [daySeason[1]], 'allRows'); //remove the first 4 const potentialRaces = calendar.slice(4); if (potentialRaces.length < 2) { //too little races return newsList; } //pick one randomly between nber 5 and 9 of the remaining races const potentialCancellations = potentialRaces.slice(0, Math.min(5, potentialRaces.length - 1)); const cancellationRace = randomPick(potentialCancellations); const originalTrackId = Number(String(cancellationRace[1]).trim()); let originalRaceId = cancellationRace[0]; let availableRaceBefore = false, availableRaceAfter = false; let newRaceTrackId = null; let newRaceDay = null; let typeOfSubstitution = null; //check if the race before is more than 7 days before or the race after is more than 7 days after const cancellationIndex = calendar.findIndex(r => r[0] === cancellationRace[0]); if (cancellationIndex > 0) { const previousRace = calendar[cancellationIndex - 1]; const dayDiff = cancellationRace[2] - previousRace[2]; if (dayDiff > 7) { availableRaceBefore = true; } } if (cancellationIndex < calendar.length - 1) { const nextRace = calendar[cancellationIndex + 1]; const dayDiff = nextRace[2] - cancellationRace[2]; if (dayDiff > 7) { availableRaceAfter = true; } } if (availableRaceAfter) { //50% chance if (Math.random() < 0.5) { //put the same race that the race after but 7 days before const nextRace = calendar[cancellationIndex + 1]; newRaceTrackId = nextRace[1]; newRaceDay = nextRace[2] - 7; typeOfSubstitution = "same_as_next"; } else { let region = continentDict[originalTrackId] || "Europe"; let racesPool = contintntRacesRegions[region].filter(tid => tid !== originalTrackId); newRaceTrackId = randomPick(racesPool); newRaceDay = cancellationRace[2]; //same day typeOfSubstitution = "different_race"; } } else if (availableRaceBefore) { //50% chance if (Math.random() < 0.5) { //put the same race that the race before but 7 days after const previousRace = calendar[cancellationIndex - 1]; newRaceTrackId = previousRace[1]; newRaceDay = previousRace[2] + 7; typeOfSubstitution = "same_as_previous"; } else { let region = continentDict[originalTrackId] || "Europe"; let racesPool = contintntRacesRegions[region].filter(tid => tid !== originalTrackId); newRaceTrackId = randomPick(racesPool); newRaceDay = cancellationRace[2]; //same day typeOfSubstitution = "different_race"; } } else { let region = continentDict[originalTrackId] || "Europe"; let racesPool = contintntRacesRegions[region].filter(tid => tid !== originalTrackId); newRaceTrackId = randomPick(racesPool); newRaceDay = cancellationRace[2]; typeOfSubstitution = "different_race"; } const originalCountry = countries_data[races_names[originalTrackId]]?.adjective || "Unknown Country"; const substituteCountry = countries_data[races_names[newRaceTrackId]]?.country || "Unknown Country"; const reasons_pool = [ "infrastructure delays", "contractual disputes", "financial uncertainty", "logistical challenges", "homologation issues", "political instability", "travel restrictions", "calendar restructuring", "environmental concerns", "permit complications with local authorities" ]; const reason = randomPick(reasons_pool); const newEntryId = `turning_point_race_substitution_${currentMonth}`; const code = races_names[Number(originalTrackId)].toLowerCase() const image = getImagePath(null, code, "race_substitution"); const titleData = { originalCountry: originalCountry, substituteCountry: substituteCountry, reason: reason, originalTrackId: originalTrackId, newRaceTrackId: newRaceTrackId, newRaceDay: newRaceDay, raceId: originalRaceId, month: currentMonth, season: daySeason[1], typeOfSubstitution: typeOfSubstitution }; const title = generateTurningPointTitle(titleData, 105, "original"); const excelDate = dateToExcel(new Date(daySeason[1], currentMonth - 1, Math.floor(Math.random() * 28) + 1)); turningPointState.raceSubstitutionOpportunities[currentMonth] = titleData; const newsEntry = { id: newEntryId, title: title, image: image, date: excelDate, data: titleData, turning_point_type: "original", type: "turning_point_race_substitution" } newsList.push(newsEntry); return newsList; } function generateInvestmentTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const months = [4, 5, 6, 7, 8, 9, 10, 11]; let newsList = []; for (let month of months) { const entryId = `turning_point_investment_${month}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); } } const maxPerSeason = Math.min(months.length, getTurningPointMax("investment", tpConfig)); if (newsList.length >= maxPerSeason) { return newsList; } //if season is odd, return empty array if (daySeason[1] % 2 === 1) { return newsList; } //if that month has already been calculated then skip chance if (turningPointState.investmentOpportunities[currentMonth] !== null) { return newsList; } const chance = getTurningPointChance("investment", tpConfig); if (Math.random() >= chance) { turningPointState.investmentOpportunities[currentMonth] = "None"; return newsList; } //rich countries with interest in motorsport, especially middle eastern const investmentCountries = ["China", "Saudi Arabia", "United Arab Emirates", "India", "Russia", "South Africa", "Qatar", "Bahrain", "Singapore", "Vietnam"]; const countryName = randomPick(investmentCountries); const countryCode = countryName.slice(0, 3).toLowerCase(); const globals = getGlobals(); let teamIds = [2, 3, 5, 6, 7, 8, 9, 10] //exclude ferrari abd mercedes if (globals.isCreateATeam) { teamIds.push(32); } const randomTeamId = randomPick(teamIds); const randomTeamName = combined_dict[randomTeamId] || "Unknown Team"; const investmentRanges = [ { share: 10, amounts: [30, 40, 50, 60] }, { share: 15, amounts: [40, 50, 60, 70] }, { share: 20, amounts: [50, 60, 75, 90] }, { share: 25, amounts: [70, 80, 90, 100] }, { share: 30, amounts: [80, 100, 120, 140] }, { share: 35, amounts: [100, 120, 150] }, { share: 40, amounts: [120, 150, 180] }, { share: 51, amounts: [150, 180, 200, 220] }, { share: 65, amounts: [200, 250, 300] }, { share: 80, amounts: [250, 300, 350, 400] } ]; const selectedRange = randomPick(investmentRanges); const investmentShare = selectedRange.share; const investmentAmount = randomPick(selectedRange.amounts); const titleData = { country: countryName, teamId: randomTeamId, teamName: randomTeamName, investmentAmount: investmentAmount, investmentShare: investmentShare, season: daySeason[1], month: currentMonth, }; const title = generateTurningPointTitle(titleData, 102, "original"); const image = getImagePath(null, countryCode, "investment"); const excelDate = dateToExcel(new Date(daySeason[1], currentMonth - 1, Math.floor(Math.random() * 28) + 1)); turningPointState.investmentOpportunities[currentMonth] = { country: countryName, teamId: randomTeamId, teamName: randomTeamName, investmentAmount: investmentAmount, investmentShare: investmentShare, season: daySeason[1], month: currentMonth, } const entryId = `turning_point_investment_${currentMonth}`; const newsEntry = { id: entryId, title: title, image: image, date: excelDate, data: titleData, turning_point_type: "original", type: "turning_point_investment" } newsList.push(newsEntry); return newsList; } function generateDriverInjuryTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { turningPointState.injuries = turningPointState.injuries || {}; const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const todayExcel = Number(daySeason[0]); const seasonYear = Number(daySeason[1]); const months = [4, 5, 6]; const maxPerSeason = Math.min(months.length, getTurningPointMax("injury", tpConfig)); const newsList = []; for (const m of months) { //should only be april, may and june, else is for testing const id = `turning_point_injury_${m}`; if (savednews[id]) newsList.push({ id, ...savednews[id] }); if (newsList.length >= maxPerSeason) { return newsList; } } //should only be april, may and june, else is for testing if (![4, 5, 6].includes(currentMonth) || turningPointState.injuries[currentMonth]) { return newsList; } const nRemainingRaces = queryDB(` SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 0 `, [seasonYear], 'singleValue'); // Need at least 5 races remaining to consider an injury if (nRemainingRaces < 5) { turningPointState.injuries[currentMonth] = "None"; return newsList; } const chance = getTurningPointChance("injury", tpConfig); if (Math.random() >= chance) { turningPointState.injuries[currentMonth] = "None"; return newsList; } const INJURY_CATALOG = [ { share: 35, type: "illness", condition: "an illness", durations: [7, 10, 14], reasons: [ "a strong viral infection requiring rest", "high fever and fatigue from a viral infection", "a severe stomach flu affecting performance" ] }, { share: 20, type: "minor_injury", condition: "a sprained wrist", durations: [14, 21, 28], reasons: [ "a minor wrist sprain during strength training", "an awkward twist of the wrist in a reaction drill", "a wrist knock after a training fall on the bike" ] }, { share: 15, type: "back_pain", condition: "a back-pain flare-up", durations: [14, 21, 28, 35], reasons: [ "a back-pain flare-up after long simulator sessions", "a lower-back muscle strain requiring physiotherapy", "persistent lumbar discomfort requiring medical rest" ] }, { share: 15, type: "concussion", condition: "a concussion", durations: [21, 28, 35, 42], reasons: [ "a light crash during private testing (concussion protocol)", "mild concussion symptoms requiring medical clearance", "dizziness and headache after an impact (FIA protocol)" ] }, { share: 10, type: "fracture", condition: "a small fracture", durations: [28, 42, 56], reasons: [ "a small collarbone fracture from cycling", "a hairline rib fracture during a bike session", "a minor hand fracture requiring immobilization" ] }, { share: 5, type: "surgery", condition: "a recent minor surgery", durations: [35, 49, 63], reasons: [ "a scheduled minor procedure with ongoing recovery", "a successful minor intervention followed by rehabilitation", "a preventive procedure to address recurring pain" ] } ]; // Weighted random pick helper function weightedPick(arr, key = "share") { const total = arr.reduce((s, it) => s + (it[key] || 0), 0); let r = Math.random() * total; for (const it of arr) { r -= (it[key] || 0); if (r <= 0) return it; } return arr[arr.length - 1]; } const pickedInjury = weightedPick(INJURY_CATALOG); const baseDuration = randomPick(pickedInjury.durations); // --- Get teams with two official drivers --- const pairs = queryDB(` SELECT con.TeamID, con.StaffID FROM Staff_Contracts con JOIN Staff_DriverData d ON d.StaffID = con.StaffID WHERE con.ContractType = 0 AND con.PosInTeam <= 2 AND (con.TeamID <= 10 OR con.TeamID = 32) `, [], 'allRows'); if (!pairs || !pairs.length) { turningPointState.injuries[currentMonth] = "None"; return newsList; } const teamToDrivers = {}; for (const row of pairs) { const teamId = Number(row[0]); const driverId = Number(row[1]); (teamToDrivers[teamId] ||= []).push(driverId); } const eligibleTeams = Object.keys(teamToDrivers).filter(t => teamToDrivers[t].length >= 2).map(Number); if (!eligibleTeams.length) { turningPointState.injuries[currentMonth] = "None"; return newsList; } // Pick random team and driver const teamId = randomPick(eligibleTeams); const teamDrivers = teamToDrivers[teamId]; const driverId = randomPick(teamDrivers); const teamName = combined_dict[teamId] || "Unknown Team"; // Get driver name let nameRow = queryDB(`SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ?`, [driverId], 'singleRow') || ["Unknown", "Driver"]; const driverName = formatNamesSimple([...nameRow, driverId]); // --- Injury dates --- const startExcel = todayExcel; const startDate = excelToDate(startExcel); // Get next race after start date const nextRaceDay = queryDB(` SELECT MIN(Day) FROM Races WHERE Day > ? `, [startExcel], 'singleValue'); const nextRaceExcel = nextRaceDay ? Number(nextRaceDay) + 2 : null; // Compute end date (extended to cover at least the next race) let endDate = new Date(startDate.getTime()); endDate.setDate(endDate.getDate() + baseDuration); if (nextRaceExcel) { const nextRaceDate = excelToDate(nextRaceExcel); if (endDate < nextRaceDate) { endDate = new Date(nextRaceDate.getTime()); } } const endExcel = dateToExcel(endDate); // Find affected races between start and end const racesAffected = queryDB(` SELECT RaceID, Day, TrackID FROM Races WHERE Day >= ? AND Day <= ? ORDER BY Day ASC `, [startExcel, endExcel], 'allRows') || []; const expectedRaceReturn = queryDB(` SELECT MIN(Day), RaceID, TrackID FROM Races WHERE Day > ? `, [endExcel], 'singleRow'); const expectedReturnRaceId = expectedRaceReturn ? Number(expectedRaceReturn[1]) : null; const expectedReturnCountry = countries_data[races_names[Number(expectedRaceReturn[2])]]?.country const reason = randomPick(pickedInjury.reasons); let reserve = {} let reserveCandidates = []; const reserveDrivers = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_Contracts con JOIN Staff_BasicData bas ON con.StaffID = bas.StaffID JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam > 2`, [teamId], 'allRows'); if (reserveDrivers.length) { reserveCandidates = []; for (const rd of reserveDrivers) { const overall = getDriverOverall(rd[2]); reserveCandidates.push({ driverId: rd[2], teamId: rd[3], rating: overall, name: formatNamesSimple([rd[0], rd[1], rd[4]])[0] }); } reserveCandidates.sort((a, b) => b.rating - a.rating); } else { return newsList; } //get the reserve driver with highest rating reserve.name = reserveCandidates[0].name; reserve.id = reserveCandidates[0].driverId; reserve.teamId = reserveCandidates[0].teamId; reserve.overall = reserveCandidates[0].rating; //if reserve overall is lower than 70 and fallback if (reserve.overall < 70 || !reserve.overall || !reserve.id) { let freeCandidates = []; const freeAgents = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE dri.StaffID NOT IN (SELECT StaffID FROM Staff_Contracts) AND gd.Retired = 0`, [], 'allRows'); if (freeAgents.length) { for (const fa of freeAgents) { const overall = getDriverOverall(fa[2]); freeCandidates.push({ driverId: fa[2], rating: overall, name: formatNamesSimple([fa[0], fa[1], fa[2]])[0] }); } freeCandidates.sort((a, b) => b.rating - a.rating); reserve.name = freeCandidates[0].name; reserve.id = freeCandidates[0].driverId; reserve.teamId = null; reserve.overall = freeCandidates[0].rating; reserve.isFreeAgent = true; reserve.futureTeamId = teamId; } } // Build data object const entryId = `turning_point_injury_${currentMonth}`; if (savednews[entryId]) return newsList; const newData = { team: teamName, teamId, driver_affected: { id: driverId, name: driverName[0], teamId }, condition: { type: pickedInjury.type, condition: pickedInjury.condition, reason, start_date: startExcel, end_date: endExcel, next_race_min_enforced: !!nextRaceExcel, races_affected: racesAffected.map(r => ({ raceId: Number(r[0]), country: countries_data[races_names[Number(r[2])]]?.country, day: Number(r[1]) })), expectedReturnRaceId: expectedReturnRaceId, expectedReturnCountry: expectedReturnCountry }, reserve_driver: reserve, month: currentMonth, season: seasonYear }; turningPointState.injuries[currentMonth] = newData; const title = generateTurningPointTitle(newData, 106, "original") const image = getImagePath(null, driverId, "injury"); const entry = { id: entryId, title, image, data: newData, date: startExcel, turning_point_type: "original", type: "turning_point_injury" }; newsList.push(entry); return newsList; } function generateEnginesTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], "singleRow"); const season = daySeason[1]; const newsList = []; const allRacesDone = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], "singleValue" ); const totalRaces = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], "singleValue" ); // Solo último mes + temporada terminada if (currentMonth < 11 || allRacesDone < totalRaces) { return newsList; } const entryId = `turning_point_engine_regulation_${season}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return newsList; } const chance = getTurningPointChance("engineRegulation", tpConfig); if (Math.random() >= chance) { return newsList; } // --- Lectura DB --- const engines = queryDB(`SELECT * FROM Custom_Engines_list`, [], "allRows"); const engineStats = queryDB(`SELECT * FROM Custom_Engines_stats`, [], "allRows"); // --- Tipo de regulación --- let changeType = "minor"; if (Math.random() < 0.1) changeType = "major"; const minorChangeAreas = [ "fuel flow monitoring", "ERS deployment limits", "MGU-K usage rules", "cooling system allowances", "gearbox durability limits", "turbo efficiency limits", "oil consumption rules" ]; const majorChangeAreas = [ "hybrid system architecture", "engine architecture layout", "combustion concept rules", "turbocharger design limits", "energy recovery system redesign", "fuel system design rules", "power unit packaging regulations" ]; const changeAreasPool = changeType === "major" ? majorChangeAreas : minorChangeAreas; const mainChangeArea = randomPick(changeAreasPool); const VAR = changeType === "major" ? 0.28 : 0.13; // major changes can cause up to ±28% change, minor up to ±15% const randBetween = (min, max) => min + Math.random() * (max - min); const clamp = (n, min, max) => Math.max(min, Math.min(max, n)); const currentStats = {}; for (const row of engineStats) { const engineId = String(row[0]); const designId = Number(row[1]); const partStat = Number(row[2]); const unitValue = Number(row[3]); // 4º valor = unitValue (según tu tabla) if (!currentStats[engineId]) currentStats[engineId] = {}; const eNum = Number(engineId); if (partStat === 15 && designId === eNum + 1) { currentStats[engineId][18] = unitValue; // ERS } else if (partStat === 15 && designId === eNum + 2) { currentStats[engineId][19] = unitValue; // Gearbox } else { currentStats[engineId][partStat] = unitValue; } } // --- 2) Decide beneficiado/perjudicado ANTES (Opción A) --- // Distribución: // minor: 35% win, 35% lose, 30% neutral // major: 45% win, 45% lose, 10% neutral const engineBias = {}; // engineId -> -1 | 0 | +1 const winners = []; const losers = []; const neutrals = []; for (const engineId of Object.keys(currentStats)) { const r = Math.random(); let bias; if (changeType === "major") { bias = r < 0.45 ? 1 : r < 0.90 ? -1 : 0; } else { bias = r < 0.35 ? 1 : r < 0.70 ? -1 : 0; } engineBias[engineId] = bias; if (bias === 1) winners.push(engineId); else if (bias === -1) losers.push(engineId); else neutrals.push(engineId); } // --- 3) Genera engineData aplicando variación SOLO en dirección del bias --- // engineData[engineId][stat] = nuevo unitValue const engineData = {}; const engineImpact = {}; // engineId -> % medio (ej 0.032 = +3.2%) para ordenar si quieres for (const engineId of Object.keys(currentStats)) { engineData[engineId] = {}; let sumPct = 0; let count = 0; for (const statKey of Object.keys(currentStats[engineId])) { const stat = Number(statKey); const cur = Number(currentStats[engineId][stat]); // No tocar 11 y 12 if (stat === 11 || stat === 12) { engineData[engineId][stat] = cur; continue; } const bias = engineBias[engineId]; let mult = 1; if (bias === 1) mult = 1 + randBetween(0, VAR); // solo sube else if (bias === -1) mult = 1 - randBetween(0, VAR); // solo baja else mult = 1 + randBetween(-VAR, VAR); // neutro: libre let next = Math.round(cur * mult); next = Math.max(0, next); next = clamp(next, 0, 100); engineData[engineId][stat] = next; if (cur > 0) { sumPct += (next - cur) / cur; count++; } } engineImpact[engineId] = count ? sumPct / count : 0; const eNum = Number(engineId); if (engineData[engineId][18] === undefined) { const ersUnit = queryDB( `SELECT UnitValue FROM Custom_Engines_Stats WHERE engineId = ? AND designId = ? AND partStat = 15`, [engineId, eNum + 1], "singleValue" ); engineData[engineId][18] = ersUnit != null ? clamp(Math.max(0, Number(ersUnit)), 0, 100) : 0; } if (engineData[engineId][19] === undefined) { const gbUnit = queryDB( `SELECT UnitValue FROM Custom_Engines_Stats WHERE engineId = ? AND designId = ? AND partStat = 15`, [engineId, eNum + 2], "singleValue" ); engineData[engineId][19] = gbUnit != null ? clamp(Math.max(0, Number(gbUnit)), 0, 100) : 0; } } // --- 4) Mapea nombres de motores para news (opcional pero útil) --- // engines viene tipo: [[1,"Ferrari"], [4,"Red Bull"], ...] const engineNameById = {}; engines.forEach(row => { engineNameById[String(row[0])] = row[1]; }); const winnerNames = winners.map(id => engineNameById[id] ?? id); const loserNames = losers.map(id => engineNameById[id] ?? id); const neutralNames = neutrals.map(id => engineNameById[id] ?? id); const titleData = { changeType, mainChangeArea, variability: VAR, engineData, // <- lo pasas a editEngines(engineData) engineBias, // <- +1/-1/0 por motor engineImpact, // <- % medio por motor winners, losers, neutrals, winnerNames, loserNames, neutralNames, season }; turningPointState.engineRegulation = titleData; const title = generateTurningPointTitle(titleData, 107, "original"); const image = getImagePath(null, "engine", "engine"); const newsDate = new Date(season, 11, Math.floor(Math.random() * 8) + 13); const excelDate = dateToExcel(newsDate); const newsEntry = { id: entryId, title, image, date: excelDate, data: titleData, turning_point_type: "original", type: "turning_point_engine_regulation" }; newsList.push(newsEntry); return newsList; } function generateAduoTurningPointsNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null, aduoTPsEnabled = false) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason?.[1]; const newsList = []; if (!season) { return newsList; } const nRacesThisSeasonRaw = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], 'singleValue'); const nRacesDoneRaw = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], 'singleValue'); const totalRaces = Number(nRacesThisSeasonRaw) || 0; const racesDone = Number(nRacesDoneRaw) || 0; if (totalRaces <= 0) { return newsList; } turningPointState.aduoTurningPoints = turningPointState.aduoTurningPoints || {}; // Turning points are unlocked after each quarter of the season (25%, 50%, 75%), // excluding the final quarter (100%). const quarterThresholds = [ { quarter: 1, minRacesDone: Math.ceil(totalRaces * 0.25), string: "1st" }, { quarter: 2, minRacesDone: Math.ceil(totalRaces * 0.50), string: "2nd" }, { quarter: 3, minRacesDone: Math.ceil(totalRaces * 0.75), string: "3rd" } ]; let enginesData = null; for (const { quarter, minRacesDone, string } of quarterThresholds) { if (racesDone < minRacesDone) continue; const entryId = `turning_point_aduo_q${quarter}_${season}`; //get the month from that race const raceInfo = queryDB(`SELECT Day FROM Races WHERE SeasonID = ? AND State = 2 ORDER BY Day ASC LIMIT ?, 1`, [season, minRacesDone - 1], 'singleRow'); const raceDay = raceInfo ? Number(raceInfo[0]) : null; const raceDate = raceDay ? excelToDate(raceDay) : null; const raceMonth = raceDate ? raceDate.getMonth() : null; // Already saved in DB -> just surface it again. if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); continue; } // Already generated this session (but not persisted yet) -> surface it again. if (turningPointState.aduoTurningPoints[entryId]) { newsList.push({ id: entryId, ...turningPointState.aduoTurningPoints[entryId] }); continue; } if (!aduoTPsEnabled) { continue; } if (!enginesData) { const engines = fetchEngines(); enginesData = engines?.[0] || []; } const getEngineStat10 = engineRow => { const stats = engineRow?.[1] || {}; const raw = stats[10] !== undefined ? stats[10] : stats["10"]; if (raw === undefined || raw === null) return null; return Number(raw); }; let bestEngine = null; let bestStat10 = -Infinity; for (const engineRow of enginesData) { const stat10 = getEngineStat10(engineRow); if (stat10 === null) continue; if (stat10 > bestStat10) { bestStat10 = stat10; bestEngine = engineRow; } } const threshold = bestStat10 * 0.92; // >8% underperformance to be affected const underperformers = enginesData.filter(engineRow => { const stat10 = getEngineStat10(engineRow); return stat10 !== null && stat10 < threshold; }); const randomImprovementPct = () => { const r = Math.random(); if (r < 0.05) { // Rare regression: -5% to 0% return Math.round(((-5 + (Math.random() * 5)) * 100)) / 100; } if (r < 0.87) { // Usual case: 1% to 7% return Math.round(((1 + (Math.random() * 6)) * 100)) / 100; } // Very rare breakout: 7% to 18% return Math.round(((7 + (Math.random() * 9)) * 100)) / 100; }; const getUpgradeTuningForEngine = (stat10) => { const best = Number(bestStat10); const current = Number(stat10); if (best <= 0) { return { minBonusPct: 0, multiplier: 1 }; } const behindPct = ((best - current) / best) * 100; if (behindPct > 35) { return { minBonusPct: 5, multiplier: 1.5 }; } if (behindPct >= 20) { return { minBonusPct: 0, multiplier: 1.25 }; } return { minBonusPct: 0, multiplier: 1 }; }; const engineImprovements = underperformers.map(engineRow => { const engineId = engineRow[0]; const name = engineRow[2]; const stats = engineRow[1] || {}; const improvements = {}; const tuning = getUpgradeTuningForEngine(getEngineStat10(engineRow)); for (const statId of Object.keys(stats)) { let pct = randomImprovementPct(); if (tuning.minBonusPct) { pct += tuning.minBonusPct; } if (tuning.multiplier !== 1) { pct *= tuning.multiplier; } improvements[statId] = Math.round(pct * 100) / 100; } return { engineId, name, improvements }; }); let titleData = { season, quarter, leader: { engineId: bestEngine?.[0], name: bestEngine?.[2], stat10: bestStat10 }, thresholdStat10: threshold, engineImprovements }; let manufacutrersAffectedStriing = ""; if (titleData.engineImprovements.length > 0) { manufacutrersAffectedStriing = titleData.engineImprovements.map(e => e.name).join(", "); //the last one should have "and" if there are more than 1 if (titleData.engineImprovements.length > 1) { const lastCommaIndex = manufacutrersAffectedStriing.lastIndexOf(", "); manufacutrersAffectedStriing = manufacutrersAffectedStriing.substring(0, lastCommaIndex) + " and" + manufacutrersAffectedStriing.substring(lastCommaIndex + 1); } } else { return newsList; // No underperformers, no turning point } titleData.quarterString = string; titleData.manufacturers = manufacutrersAffectedStriing; const title = generateTurningPointTitle(titleData, 109, "original"); const image = getImagePath(null, "engine", "engine"); const newsEntry = { id: entryId, title, image, date: dateToExcel(new Date(season, raceMonth, 8 + quarter)), // Mid-month date for the news data: titleData, turning_point_type: "original", type: "turning_point_aduo" }; newsList.push(newsEntry); } return newsList; } function generateYoungDriversTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { const FREE_AGENT_MAX_AGE = 19; const YOUNG_DRIVER_MAX_PER_SERIES = 3; const FREE_AGENT_MAX = 3; const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason?.[1]; const newsList = []; if (!season) { return newsList; } const allRacesDone = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], "singleValue" ); const totalRaces = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], "singleValue" ); if (currentMonth < 11 || allRacesDone < totalRaces) { return newsList; } const entryId = `turning_point_young_drivers_${season}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return newsList; } const chance = getTurningPointChance("youngDrivers", tpConfig); console.log("Young Drivers Turning Point Chance:", chance); if (Math.random() >= chance) { return newsList; } const currentDay = daySeason?.[0] ?? 0; const f2Rows = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, bas.DOB, con.TeamID, sta.Position, sta.Points FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID JOIN Staff_BasicData bas ON bas.StaffID = dri.StaffID LEFT JOIN Races_DriverStandings sta ON sta.DriverID = dri.StaffID AND sta.SeasonID = ? AND sta.RaceFormula = 2 WHERE con.ContractType = 0 AND con.TeamID BETWEEN 11 AND 21`, [season], "allRows" ); const f2Map = new Map(); f2Rows.forEach(row => { const [firstName, lastName, driverId, dob, teamId, position, points] = row; const age = (dob != null && currentDay != null) ? Math.floor((currentDay - dob) / 365.25) : null; const driverNum = Number(driverId); if (f2Map.has(driverNum)) return; const [nameFormatted] = formatNamesSimple([firstName, lastName, driverId]); const teamName = teamId ? combined_dict[teamId] : null; const overall = getDriverOverall(driverId); f2Map.set(driverNum, { driverId: driverNum, name: nameFormatted, age, position: position != null ? Number(position) : null, points: points != null ? Number(points) : null, teamId: teamId != null ? Number(teamId) : null, team: teamName || "", series: "F2", overall: Number(overall || 0) }); }); const f2AllDrivers = Array.from(f2Map.values()); const f2AgeValues = f2AllDrivers.map(p => p.age).filter(age => typeof age === "number"); const f2AverageAge = f2AgeValues.length ? f2AgeValues.reduce((sum, age) => sum + age, 0) / f2AgeValues.length : null; const f2AgeCut = f2AverageAge != null ? f2AverageAge : Number.POSITIVE_INFINITY; const f2Eligible = f2AllDrivers.filter(p => typeof p.age === "number" && p.age <= f2AgeCut); const f2Prospects = f2Eligible .sort((a, b) => (b.overall - a.overall) || (a.age - b.age)) .slice(0, YOUNG_DRIVER_MAX_PER_SERIES); const f3Rows = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, bas.DOB, con.TeamID, sta.Position, sta.Points FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID JOIN Staff_BasicData bas ON bas.StaffID = dri.StaffID LEFT JOIN Races_DriverStandings sta ON sta.DriverID = dri.StaffID AND sta.SeasonID = ? AND sta.RaceFormula = 3 WHERE con.ContractType = 0 AND con.TeamID BETWEEN 22 AND 31`, [season], "allRows" ); const f3Map = new Map(); f3Rows.forEach(row => { const [firstName, lastName, driverId, dob, teamId, position, points] = row; const age = (dob != null && currentDay != null) ? Math.floor((currentDay - dob) / 365.25) : null; const driverNum = Number(driverId); if (f3Map.has(driverNum)) return; const [nameFormatted] = formatNamesSimple([firstName, lastName, driverId]); const teamName = teamId ? combined_dict[teamId] : null; const overall = getDriverOverall(driverId); f3Map.set(driverNum, { driverId: driverNum, name: nameFormatted, age, position: position != null ? Number(position) : null, points: points != null ? Number(points) : null, teamId: teamId != null ? Number(teamId) : null, team: teamName || "", series: "F3", overall: Number(overall || 0) }); }); const f3AllDrivers = Array.from(f3Map.values()); const f3AgeValues = f3AllDrivers.map(p => p.age).filter(age => typeof age === "number"); const f3AverageAge = f3AgeValues.length ? f3AgeValues.reduce((sum, age) => sum + age, 0) / f3AgeValues.length : null; const f3AgeCut = f3AverageAge != null ? f3AverageAge : Number.POSITIVE_INFINITY; const f3Eligible = f3AllDrivers.filter(p => typeof p.age === "number" && p.age <= f3AgeCut); const f3Prospects = f3Eligible .sort((a, b) => (b.overall - a.overall) || (a.age - b.age)) .slice(0, YOUNG_DRIVER_MAX_PER_SERIES); const usedIds = new Set([...f2Prospects, ...f3Prospects].map(p => p.driverId)); const extraRows = queryDB( `SELECT bas.FirstName, bas.LastName, bas.DOB, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE gd.Retired = 0 AND dri.StaffID NOT IN (SELECT StaffID FROM Staff_Contracts WHERE ContractType = 0)`, [], "allRows" ); const extraCandidates = []; extraRows.forEach(row => { const [firstName, lastName, dob, driverId] = row; const driverNum = Number(driverId); if (usedIds.has(driverNum)) return; const age = (dob != null && currentDay != null) ? Math.floor((currentDay - dob) / 365.25) : null; if (age == null || age > FREE_AGENT_MAX_AGE) return; const [nameFormatted] = formatNamesSimple([firstName, lastName, driverId]); const overall = getDriverOverall(driverId); extraCandidates.push({ driverId: driverNum, name: nameFormatted, age, position: null, points: null, teamId: null, team: "", series: "Regional formulas", overall: Number(overall || 0) }); }); const freeAgentProspects = extraCandidates .sort((a, b) => (b.overall - a.overall) || (a.age - b.age)) .slice(0, FREE_AGENT_MAX); const titleProspects = []; const titleSeen = new Set(); const pushTitleProspect = (prospect) => { if (!prospect || titleSeen.has(prospect.driverId)) return; titleProspects.push(prospect); titleSeen.add(prospect.driverId); }; if (f2Prospects.length > 0) { pushTitleProspect(f2Prospects[0]); } if (f3Prospects.length > 0) { pushTitleProspect(f3Prospects[0]); } if (f2Prospects.length === 0 || f3Prospects.length === 0) { freeAgentProspects.forEach(pushTitleProspect); } if (titleProspects.length < 2) { f2Prospects.slice(1).forEach(pushTitleProspect); f3Prospects.slice(1).forEach(pushTitleProspect); } const titleNames = titleProspects.slice(0, 2).map(p => p.name); const titleData = { season, driver1: titleNames[0] || "", driver2: titleNames[1] || "", driver3: titleNames[2] || "", f2Prospects, f3Prospects, freeAgentProspects, prospects: [...f2Prospects, ...f3Prospects, ...freeAgentProspects] }; turningPointState.youngDrivers = titleData; const title = generateTurningPointTitle(titleData, 108, "original"); const image = getImagePath(null, null, "young"); const newsDate = new Date(season, 11, Math.floor(Math.random() * 8) + 13); const excelDate = dateToExcel(newsDate); const newsEntry = { id: entryId, title, image, date: excelDate, data: titleData, turning_point_type: "original", type: "turning_point_young_drivers" }; newsList.push(newsEntry); return newsList; } function applyYoungDriversBoost(turningPointData) { const prospects = turningPointData?.prospects || []; if (!prospects.length) return; const uniqueDriverIds = [...new Set(prospects.map(p => p.driverId).filter(Boolean))]; uniqueDriverIds.forEach(driverId => { boostDriverStats(driverId); boostDriverGrowth(driverId); }); } function boostDriverStats(driverId) { const YOUNG_DRIVER_STAT_BOOST_MIN = 1; const YOUNG_DRIVER_STAT_BOOST_MAX = 4; driverStats.forEach(statId => { const currentValue = queryDB( `SELECT Val FROM Staff_performanceStats WHERE StaffID = ? AND StatID = ?`, [driverId, statId], "singleValue" ); const boost = randomIntBetween(YOUNG_DRIVER_STAT_BOOST_MIN, YOUNG_DRIVER_STAT_BOOST_MAX); const baseValue = currentValue != null ? Number(currentValue) : 50; const nextValue = clampValue(baseValue + boost, 0, 100); if (currentValue == null) { queryDB( `INSERT INTO Staff_performanceStats (StaffID, StatID, Val, Max) VALUES (?, ?, ?, 100)`, [driverId, statId, nextValue], "run" ); } else { queryDB( `UPDATE Staff_performanceStats SET Val = ? WHERE StaffID = ? AND StatID = ?`, [nextValue, driverId, statId], "run" ); } }); } function boostDriverGrowth(driverId) { const YOUNG_DRIVER_GROWTH_BOOST_MIN = 1; const YOUNG_DRIVER_GROWTH_BOOST_MAX = 4; const currentValue = queryDB( `SELECT Improvability FROM Staff_DriverData WHERE StaffID = ?`, [driverId], "singleValue" ); const boost = randomIntBetween(YOUNG_DRIVER_GROWTH_BOOST_MIN, YOUNG_DRIVER_GROWTH_BOOST_MAX); const baseValue = currentValue != null ? Number(currentValue) : 0; const nextValue = clampValue(baseValue + boost, 0, 100); queryDB( `UPDATE Staff_DriverData SET Improvability = ? WHERE StaffID = ?`, [nextValue, driverId], "run" ); } function clampValue(value, min, max) { return Math.max(min, Math.min(max, value)); } function randomIntBetween(min, max) { const minVal = Math.ceil(min); const maxVal = Math.floor(max); return Math.floor(Math.random() * (maxVal - minVal + 1)) + minVal; } function generateTechnicalDirectiveTurningPointNews(currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { let newsList = []; //get previous technical directive turning point news const months = [6, 9]; for (let month of months) { const entryId = `turning_point_technical_directive_${month}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); } } const maxPerSeason = Math.min(months.length, getTurningPointMax("technicalDirective", tpConfig)); if (newsList.length >= maxPerSeason) { return newsList; } if ((currentMonth !== 6 && currentMonth !== 9) || turningPointState.technicalDirectives[currentMonth] === "None") { return newsList; } const entryId = `turning_point_technical_directive_${currentMonth}`; if (savednews[entryId]) { return newsList; } const chance = getTurningPointChance("technicalDirective", tpConfig); if (Math.random() >= chance) { turningPointState.technicalDirectives[currentMonth] = "None"; return newsList; } const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const parts = [3, 4, 5, 6, 7, 8] const partId = randomPick(parts); const partName = part_full_names[partId].toLowerCase() || "Unknown Part"; const globals = getGlobals(); let teamIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if (globals.isCreateATeam) { teamIds.push(32); } const excelDate = dateToExcel(new Date(daySeason[1], currentMonth - 1, Math.floor(Math.random() * 28) + 1)); const performance = getPerformanceAllTeams(excelDate, null, globals.isCreateATeam) const championship = queryDB(`SELECT TeamID, Points, Position FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = 1 ORDER BY Position`, [daySeason[1]], 'allRows'); const constructorsStandings = {}; for (const row of championship) { const teamId = Number(row[0]); constructorsStandings[teamId] = { points: Number(row[1]), rank: Number(row[2]) }; } const capsByPart = { 3: 7.5, //chasis 4: 4, //front wing 5: 4, //rear wing 6: 4, //underfloor 7: 7, //suspension 8: 5 //sidepod } const cap = capsByPart[partId] || 5; // máximo +/- const standingsWeight = 0.25; // 0 = ignora standings; prueba 0.2–0.3 si quieres mezclar un poco const zeroSum = true; // intenta balance neto ~0 en compresión const spreadCapMultiplier = 1.45; // Higher = more "spread" in spread mode let effectOnEachteam = {}; // Normaliza tipos de teamIds por si vienen como strings const ids = teamIds.map(id => Number(id)); // Helper clamp const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v)); const modeRoll = Math.random(); const mode = modeRoll < 0.30 ? "random" : modeRoll < 0.40 ? "compact" : "spread"; // 30% / 10% / 60% if (mode === "random") { // --- MODO RANDOM PURO (30%) --- for (const teamId of ids) { const v = (Math.random() * 2 * cap) - cap; // [-cap, cap] effectOnEachteam[teamId] = { performanceGainLoss: v.toFixed(2), teamName: combined_dict[teamId] || "Unknown Team" }; } } else { const isSpread = mode === "spread"; const direction = isSpread ? 1 : -1; // compact: better teams lose, spread: better teams gain const effectiveCap = isSpread ? (cap * spreadCapMultiplier) : cap; // --- MODO COMPACT (10%) / SPREAD (60%) basado en rendimiento (+ opcional standings) --- const vals = ids .map(id => performance[id]) .filter(v => typeof v === "number"); const mean = vals.length ? (vals.reduce((a, b) => a + b, 0) / vals.length) : 0; let maxAbsDev = 1e-9; // evita div/0 for (const id of ids) { const dev = ((performance[id] ?? mean) - mean); if (Math.abs(dev) > maxAbsDev) maxAbsDev = Math.abs(dev); } // Precompute máximo de puntos para normalizar standings si se usa const maxPts = Math.max(...ids.map(id => constructorsStandings[id]?.points ?? 0), 1); // Calcula efectos crudos const raw = ids.map(teamId => { const score = performance[teamId] ?? mean; const norm = (score - mean) / maxAbsDev; // [-1, 1] // efecto por rendimiento (compact/spread) let eff = direction * norm * effectiveCap; // mezcla ligera con standings (compact/spread) if (standingsWeight > 0) { const pts = constructorsStandings[teamId]?.points ?? 0; const ptsNorm = (pts / maxPts); // 0..1 const standingsEff = direction * (ptsNorm - 0.5) * effectiveCap; // centra en 0.5 eff = (1 - standingsWeight) * eff + standingsWeight * standingsEff; } // un pelín de ruido para que no sea tan lineal eff += (Math.random() * 0.3) - 0.15; return { teamId, eff }; }); // Cero-suma (opcional) let adjusted = raw; if (zeroSum && adjusted.length) { const avg = adjusted.reduce((a, r) => a + r.eff, 0) / adjusted.length; adjusted = adjusted.map(r => ({ teamId: r.teamId, eff: r.eff - avg })); } // clamp + salida for (const { teamId, eff } of adjusted) { effectOnEachteam[teamId] = { performanceGainLoss: clamp(eff, -effectiveCap, effectiveCap).toFixed(2), teamName: combined_dict[teamId] || "Unknown Team", teamId: teamId }; } } const possibleReasons = ["improve safety", "reduce costs", "standardize components", "enhance performance parity", "improve racing", "address a grey area in the regulations"]; const reason = randomPick(possibleReasons); const titleData = { component: partName, componentId: partId, effectOnEachteam: effectOnEachteam, month: currentMonth, reason: reason, season: daySeason[1] } const title = generateTurningPointTitle(titleData, 100, "original"); const image = getImagePath(null, partId, "technical"); //generate a RANDOM date in the current month turningPointState.technicalDirectives[currentMonth] = { component: partName, componentId: partId, effectOnEachteam: effectOnEachteam } const newData = { id: entryId, title: title, image: image, date: excelDate, data: titleData, turning_point_type: "original", type: "turning_point_technical_directive" } newsList.push(newData); return newsList; } function generateMidSeasonTransfersTurningPointNews(monthsDone, currentMonth, savednews = {}, turningPointState = {}, tpConfig = null) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); let newsList = []; const months = [5, 6, 7]; for (let month of months) { const entryId = `turning_point_transfer_${month}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); } } const maxPerSeason = Math.min(months.length, getTurningPointMax("midSeasonTransfers", tpConfig)); if (newsList.length >= maxPerSeason) { return newsList; } if (![5, 6, 7].includes(currentMonth) || turningPointState.transfers[currentMonth]) { return newsList; } const chance = getTurningPointChance("midSeasonTransfers", tpConfig); if (Math.random() >= chance) { turningPointState.transfers[currentMonth] = "None"; return newsList; } const driversTeamPoints = queryDB(` SELECT res.DriverID, con.TeamID, res.Points AS TotalPoints FROM Races_DriverStandings res JOIN Staff_Contracts con ON res.DriverID = con.StaffID AND con.ContractType = 0 AND con.PosInTeam <= 2 WHERE res.SeasonID = ? AND res.RaceFormula = 1 `, [daySeason[1]], "allRows"); const teamsById = {}; for (const row of driversTeamPoints) { const teamId = Number(row[1]); const driverId = Number(row[0]); const pts = Number(row[2]) || 0; if (!teamsById[teamId]) { teamsById[teamId] = { total: 0, drivers: {} }; } teamsById[teamId].total += pts; teamsById[teamId].drivers[driverId] = (teamsById[teamId].drivers[driverId] || 0) + pts; } const teamsWithImbalance = []; for (const [teamIdStr, data] of Object.entries(teamsById)) { const teamId = Number(teamIdStr); const total = data.total || 0; if (total <= 0) continue; const hasCarry = Object.values(data.drivers).some(pts => (pts / total) > 0.70); if (hasCarry) teamsWithImbalance.push(teamId); } const usedTeams = new Set(); if (turningPointState?.transfers) { for (const m of [5, 6, 7]) { const tp = turningPointState.transfers[m]; if (tp && tp !== "None" && tp?.teamId != null) { usedTeams.add(Number(tp.teamId)); } } } const candidateTeams = teamsWithImbalance.filter(tid => !usedTeams.has(tid)); if (candidateTeams.length === 0) { turningPointState.transfers[currentMonth] = "None"; return newsList; } const randomTeam = randomPick(candidateTeams); const randomTeamName = combined_dict[randomTeam] || "Unknown Team"; const entryId = `turning_point_transfer_${currentMonth}`; if (savednews[entryId]) { return newsList; } //the driver from the randomteam with less points let driverOut = teamsById[randomTeam] ? Object.entries(teamsById[randomTeam].drivers).sort((a, b) => a[1] - b[1])[0] : null; let driverIn, driverSubstitute, driverInTeamId, driverSubstituteTeamId; if (randomTeam === 3) { if (combined_dict[8] === "Alpha Tauri" || combined_dict[8] === "Visa Cashapp RB") { //red bull special case driverIn = Object.entries(teamsById[8].drivers).sort((a, b) => b[1] - a[1])[0]; driverInTeamId = 8; } else { const teamsByTotalPoints = Object.entries(teamsById).sort((a, b) => a[1].total - b[1].total); const lowerTeams = teamsByTotalPoints.filter(entry => entry[1].total < teamsById[3].total).map(entry => entry[0]); //get all the drivers from those teams and order them by rating const candidates = []; for (const tId of lowerTeams) { if (teamsById[tId]) { for (const [dId, pts] of Object.entries(teamsById[tId].drivers)) { let rating = getDriverOverall(dId); candidates.push({ driverId: dId, points: pts, teamId: tId, rating }); } } } candidates.sort((a, b) => b.rating - a.rating); if (candidates.length > 0) { driverIn = [candidates[0].driverId, candidates[0].points]; driverInTeamId = candidates[0].teamId; } //fallback if not driverIn, get the best free agent if (!driverIn) { const freeAgents = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE dri.StaffID NOT IN (SELECT StaffID FROM Staff_Contracts) AND gd.Retired = 0`, [], 'allRows'); if (freeAgents.length) { for (const fa of freeAgents) { const overall = getDriverOverall(fa[2]); fa.push(overall); } freeAgents.sort((a, b) => b[3] - a[3]); driverIn = [freeAgents[0][2], freeAgents[0][3]]; } } } } else { const teamsByTotalPoints = Object.entries(teamsById).sort((a, b) => a[1].total - b[1].total); const bottom3Teams = teamsByTotalPoints.slice(0, 3).map(entry => entry[0]); const top4Teams = teamsByTotalPoints.slice(-4).map(entry => entry[0]); const teamToCompare = String(randomTeam); if (bottom3Teams.includes(teamToCompare)) { const reserveDrivers = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_Contracts con JOIN Staff_BasicData bas ON con.StaffID = bas.StaffID JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam > 2`, [randomTeam], 'allRows'); if (reserveDrivers.length) { //get the best reserve driver by overall const reserveCandidates = []; for (const rd of reserveDrivers) { const overall = getDriverOverall(rd[2]); reserveCandidates.push({ driverId: rd[2], teamId: rd[3], rating: overall }); } reserveCandidates.sort((a, b) => b.rating - a.rating); driverIn = [reserveCandidates[0].driverId, reserveCandidates[0].rating]; driverInTeamId = reserveCandidates[0].teamId; } } else if (top4Teams.includes(teamToCompare)) { const teamsByTotalPoints = Object.entries(teamsById).sort((a, b) => a[1].total - b[1].total); const lowerTeams = teamsByTotalPoints.filter(entry => entry[1].total < teamsById[randomTeam].total).map(entry => entry[0]); //get all the drivers from those teams and order them by rating const candidates = []; for (const tId of lowerTeams) { if (teamsById[tId]) { for (const [dId, pts] of Object.entries(teamsById[tId].drivers)) { let rating = getDriverOverall(dId); candidates.push({ driverId: dId, points: pts, teamId: tId, rating }); } } } candidates.sort((a, b) => b.rating - a.rating); if (candidates.length > 0) { //get a random pick from the top 5 candidates const topCandidates = candidates.slice(0, 5); const selectedCandidate = randomPick(topCandidates); driverIn = [selectedCandidate.driverId, selectedCandidate.points]; driverInTeamId = selectedCandidate.teamId; const freeAgents = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE dri.StaffID NOT IN (SELECT StaffID FROM Staff_Contracts) AND gd.Retired = 0`, [], 'allRows'); if (freeAgents.length) { for (const fa of freeAgents) { const overall = getDriverOverall(fa[2]); fa.push(overall); } freeAgents.sort((a, b) => b[3] - a[3]); driverSubstitute = [freeAgents[0][2], freeAgents[0][3]]; } } } else { const candidates = []; for (const tId of bottom3Teams.filter(tid => tid != randomTeam)) { if (teamsById[tId]) { for (const [dId, pts] of Object.entries(teamsById[tId].drivers)) { candidates.push({ driverId: dId, points: pts, teamId: tId }); } } } candidates.sort((a, b) => b.points - a.points); if (candidates.length > 0) { driverIn = [candidates[0].driverId, candidates[0].points]; driverInTeamId = candidates[0].teamId; const reserveDrivers = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_Contracts con JOIN Staff_BasicData bas ON con.StaffID = bas.StaffID JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam > 2`, [driverInTeamId], 'allRows'); if (reserveDrivers.length) { const reserveCandidates = []; for (const rd of reserveDrivers) { const overall = getDriverOverall(rd[2]); reserveCandidates.push({ driverId: rd[2], teamId: rd[3], rating: overall }); } reserveCandidates.sort((a, b) => b.rating - a.rating); driverSubstitute = [reserveCandidates[0].driverId, reserveCandidates[0].rating]; driverSubstituteTeamId = reserveCandidates[0].teamId; } else { const freeAgents = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE dri.StaffID NOT IN (SELECT StaffID FROM Staff_Contracts) AND gd.Retired = 0`, [], 'allRows'); if (freeAgents.length) { for (const fa of freeAgents) { const overall = getDriverOverall(fa[2]); fa.push(overall); } freeAgents.sort((a, b) => b[3] - a[3]); driverSubstitute = [freeAgents[0][2], freeAgents[0][3]]; } } } } } const excelDate = dateToExcel(new Date(daySeason[1], currentMonth - 1, Math.floor(Math.random() * 28) + 1)); let driverOutName = driverOut ? queryDB(`SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ?`, [driverOut[0]], 'singleRow') : null; driverOutName.push(driverOut[0], driverOut[1]); driverOutName = formatNamesSimple(driverOutName || ["Unknown", "Driver"]); let driverInName = driverIn ? queryDB(`SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ?`, [driverIn[0]], 'singleRow') : null; driverInName.push(driverIn[0], driverIn[1]); driverInName = formatNamesSimple(driverInName || ["Unknown", "Driver"]); let driverSubstituteName = []; if (driverSubstitute) { driverSubstituteName = queryDB(`SELECT FirstName, LastName FROM Staff_BasicData WHERE StaffID = ?`, [driverSubstitute[0]], 'singleRow'); driverSubstituteName.push(driverSubstitute[0], driverSubstitute[1]); driverSubstituteName = formatNamesSimple(driverSubstituteName || ["Unknown", "Driver"]); } const newData = { team: randomTeamName, teamId: randomTeam, driver_out: { id: driverOutName[1], name: driverOutName[0], teamId: randomTeam }, driver_in: { id: driverInName[1], name: driverInName[0], teamId: driverInTeamId }, driver_substitute: driverSubstitute ? { id: driverSubstituteName[1], name: driverSubstituteName[0], teamId: driverSubstituteTeamId } : null, month: currentMonth, season: daySeason[1] }; turningPointState.transfers[currentMonth] = newData; //tendria que ser currentMonth, pero para pruebas lo dejo fijo en junio const title = generateTurningPointTitle(newData, 101, "original"); const image = getImagePath(null, driverOutName[1], "transfer"); const newEntry = { id: entryId, title, image, data: newData, date: excelDate, turning_point_type: "original", type: "turning_point_transfer" }; newsList.push(newEntry); return newsList; } function generateDSQTurningPointNews(racesDone, savednews = {}, turningPointState = {}, tpConfig = null) { const last3Races = racesDone.slice(-3); let newsList = []; let forcedCleanSeason = false; const maxPerSeason = getTurningPointMax("dsq", tpConfig); // Populate newsList with existing news from savednews that correspond to illegal races if (turningPointState.ilegalRaces) { turningPointState.ilegalRaces.forEach(raceData => { const entryId = `turning_point_dsq_${raceData.race_id}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); //if there is alredy two ilegal races, the rest of the season will be clean if (turningPointState.ilegalRaces.length >= maxPerSeason) forcedCleanSeason = true; } }); } const racesNotChecked = []; for (const r of last3Races) { if (!turningPointState.checkedRaces.includes(r)) { racesNotChecked.push(r); turningPointState.checkedRaces.push(r); } } if (racesNotChecked.length === 0) { return newsList; // All recent races checked, return existing news } const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const chance = getTurningPointChance("dsq", tpConfig); if (Math.random() > chance || forcedCleanSeason) { //testing, should be 0.08 return newsList; // Random chance to not generate } const raceId = randomPick(racesNotChecked); // Pick from only the new races to check const raceDate = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); const teamsWithPoints = queryDB(`SELECT TeamID, SUM(Points) FROM Races_Results WHERE RaceID = ? AND Points > 0 GROUP BY TeamID`, [raceId], 'allRows'); if (!teamsWithPoints.length) { return newsList; } const teamRow = randomPick(teamsWithPoints); const teamId = teamRow[0]; const teamName = combined_dict[teamId] || "Unknown Team"; const entryId = `turning_point_dsq_${raceId}`; if (savednews[entryId]) { // This case should ideally not be hit if logic is correct, but as a safeguard: if (!newsList.some(item => item.id === entryId)) { newsList.push({ id: entryId, ...savednews[entryId] }); } return newsList; } const components = ["engine brake map", "fuel flow", "front wing", "rear wing", "diffuser", "floor", "brake ducts", "suspension", "gearbox", "cooling system", "hydraulics", "clutch", "plank wear"]; const component = randomPick(components); let driver1, driver2; const drivers = queryDB(`SELECT bas.FirstName, bas.LastName, res.TeamID, res.Points, bas.StaffID, res.FinishingPos FROM Races_Results res JOIN Staff_BasicData bas ON res.DriverID = bas.StaffID WHERE res.RaceID = ? AND res.TeamID = ?`, [raceId, teamId], 'allRows'); drivers.forEach((d, idx) => { const nameFormatted = formatNamesSimple(d); if (idx === 0) { driver1 = { name: nameFormatted[0], points: d[3], position: d[5], driverId: d[4] }; } else if (idx === 1) { driver2 = { name: nameFormatted[0], points: d[3], position: d[5], driverId: d[4] }; } }); const titleData = { team: teamName, adjective: getCircuitInfo(raceId).adjective, circuit: getCircuitInfo(raceId).circuit, country: getCircuitInfo(raceId).country, race_id: raceId, component: component, teamId: teamId, currentSeason: daySeason[1], driver_1: driver1, driver_2: driver2 } turningPointState.ilegalRaces.push(titleData); const title = generateTurningPointTitle(titleData, 103, "original"); const image = getImagePath(null, null, "dsq"); const newEntry = { id: entryId, title, image, data: titleData, date: raceDate + 2, turning_point_type: "original", type: "turning_point_dsq" } newsList.push(newEntry); return newsList; } function getMaxPointsForRace(raceId, pointsSchema, seasonId = null) { let maxPoints = parseInt(pointsSchema.twoBiggestPoints[0]) let fastestLapPoint = parseInt(pointsSchema.fastestLapBonusPoint) let polePositionPoint = parseInt(pointsSchema.polePositionBonusPoint) let isLastraceDouble = parseInt(pointsSchema.isLastraceDouble) const isSprint = queryDB(`SELECT WeekendType FROM Races WHERE RaceID = ?`, [raceId], 'singleValue') === 1; const maxSprintPoints = 8; const isLastRaceOfSeason = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ?`, [seasonId], 'singleValue') === raceId; if (isLastRaceOfSeason && isLastraceDouble) { maxPoints *= 2; } if (isSprint) { maxPoints += parseInt(maxSprintPoints); } if (fastestLapPoint) { maxPoints += parseInt(fastestLapPoint); } if (polePositionPoint) { maxPoints += parseInt(polePositionPoint); } return parseInt(maxPoints); } function championshipStatus( raceIdToCheck, pointsSchema, allSeasonRaces, leader, rival, currentSeason ) { const raceInfo = allSeasonRaces.find(r => r.id === raceIdToCheck); if (!raceInfo) throw new Error("Carrera no encontrada"); // 2) Lista de todas las carreras desde esta (incluida) en adelante const remainingRaces = allSeasonRaces .filter(r => r.day >= raceInfo.day) .map(r => r.id); // 3) Suma de puntos máximos que puede conseguir el rival const maxPointsForRival = remainingRaces .reduce((sum, id) => sum + getMaxPointsForRace(id, pointsSchema, currentSeason) , 0); // === COMPROBACIÓN A: ¿YA es campeón antes de la carrera? === // Si rival.points + maxPointsForRival < leader.points entonces no le alcanzan ni sumando TODO const alreadyChampion = (rival.points + maxPointsForRival) < leader.points; // === COMPROBACIÓN B: ¿Se corona EN esta carrera? === // Puntos máximos que puede sumar el líder en esta carrera const maxPointsThisRace = getMaxPointsForRace( raceIdToCheck, pointsSchema, currentSeason ); // Puntos máximos que puede sumar el rival **solo en las posteriores** a esta carrera const futureRaces = allSeasonRaces .filter(r => r.day > raceInfo.day) .map(r => r.id); const maxPointsFutureOnly = futureRaces .reduce((sum, id) => sum + getMaxPointsForRace(id, pointsSchema, currentSeason) , 0); // Si líder + maxPointsThisRace > rival + maxPointsFutureOnly, se corona EN esta carrera const clinchThisRace = (leader.points + maxPointsThisRace) > (rival.points + maxPointsFutureOnly); return { alreadyChampion, clinchThisRace }; } function generateChampionMilestones(racesDone, savednews = {}) { const pointsSchema = fetchPointsRegulations(); const ps = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); if (!ps) return []; const currentSeason = ps[1]; const allSeasonRacesQuery = queryDB( `SELECT RaceID, Day, TrackID FROM Races WHERE SeasonID = ? ORDER BY Day ASC`, [currentSeason], 'allRows' ); if (!allSeasonRacesQuery || allSeasonRacesQuery.length === 0) return []; const allRaces = allSeasonRacesQuery.map(r => ({ id: r[0], day: r[1], trackId: r[2] })); const totalRaces = allRaces.length; const halfIndex = Math.floor(totalRaces / 2); const seasonResults = fetchSeasonResults(currentSeason); if (!seasonResults) return []; const doneSet = new Set(racesDone ?? []); let lastDoneIdx = -1; for (let i = 0; i < allRaces.length; i++) { if (doneSet.has(allRaces[i].id)) lastDoneIdx = i; } if (lastDoneIdx < halfIndex) return []; const out = []; const iStart = halfIndex; const prevRaceIdStart = iStart > 0 ? allRaces[iStart - 1].id : 0; const standingsBeforeStart = rebuildStandingsUntil(seasonResults, prevRaceIdStart); let wasAlreadyChampionBeforeThisRace = false; if (standingsBeforeStart?.driverStandings?.length >= 2) { const leaderS = standingsBeforeStart.driverStandings[0]; const rivalS = standingsBeforeStart.driverStandings[1]; if (leaderS && rivalS) { const statusAtStart = championshipStatus( allRaces[iStart].id, pointsSchema, allRaces, leaderS, rivalS, currentSeason ); wasAlreadyChampionBeforeThisRace = !!statusAtStart?.alreadyChampion; } } const iEnd = Math.min(lastDoneIdx + 1, allRaces.length - 1); for (let i = iStart; i <= iEnd; i++) { const race = allRaces[i]; const prevRaceId = i > 0 ? allRaces[i - 1].id : 0; const standingsBefore = rebuildStandingsUntil(seasonResults, prevRaceId); if (standingsBefore?.driverStandings?.length >= 2) { const leaderB = standingsBefore.driverStandings[0]; const rivalB = standingsBefore.driverStandings[1]; if (leaderB && rivalB && leaderB.points != null && rivalB.points != null) { const statusAtRace = championshipStatus( race.id, pointsSchema, allRaces, leaderB, rivalB, currentSeason ); if (statusAtRace?.clinchThisRace && !wasAlreadyChampionBeforeThisRace) { const newsId = `${currentSeason}_potential_champion_${race.id}`; if (savednews && savednews[newsId]) { out.push({ id: newsId, ...savednews[newsId] }); } else { const raceInfo = getCircuitInfo(race.id); const jsDate = excelToDate(race.day); jsDate.setDate(jsDate.getDate() - 2); const finalNewsDateExcel = dateToExcel(jsDate); const code = races_names[Number(race.trackId)].toLowerCase(); const image = getImagePath(leaderB.teamId, code, "champion"); const title = generateTitle({ driver_name: leaderB.name, circuit: raceInfo.circuit, country: raceInfo.country, adjective: raceInfo.adjective, season_year: currentSeason }, 8); out.push({ id: newsId, type: "potential_champion", title, date: finalNewsDateExcel, image, overlay: null, text: null, data: { raceId: race.id, season_year: currentSeason, driver_id: leaderB.driverId, driver_team_id: leaderB.teamId, driver_name: leaderB.name, driver_points: leaderB.points, rival_driver_id: rivalB.driverId, rival_driver_name: rivalB.name, rival_points: rivalB.points, circuit_name: raceInfo.circuit, country_name: raceInfo.country, adjective: raceInfo.adjective } }); } } } } const standingsAfter = rebuildStandingsUntil(seasonResults, race.id); let alreadyChampionBeforeNext = false; if (standingsAfter?.driverStandings?.length >= 2) { const leaderA = standingsAfter.driverStandings[0]; const rivalA = standingsAfter.driverStandings[1]; if (leaderA && rivalA && leaderA.points != null && rivalA.points != null) { const hasNext = i + 1 < allRaces.length; if (hasNext) { const nextRace = allRaces[i + 1]; const statusBeforeNext = championshipStatus( nextRace.id, pointsSchema, allRaces, leaderA, rivalA, currentSeason ); alreadyChampionBeforeNext = !!statusBeforeNext?.alreadyChampion; } else { alreadyChampionBeforeNext = (leaderA.points >= rivalA.points); } if (alreadyChampionBeforeNext && !wasAlreadyChampionBeforeThisRace) { const newsId = `${currentSeason}_world_champion`; const doneRace = queryDB(`SELECT State FROM Races WHERE RaceID = ?`, [race.id], 'singleValue'); if (doneRace !== 2) { // Si la carrera no está marcada como "hecha", no generar la noticia continue; } if (savednews && savednews[newsId]) { out.push({ id: newsId, ...savednews[newsId] }); } else { const raceInfo = getCircuitInfo(race.id); const finalNewsDateExcel = race.day + 1; const code = races_names[Number(race.trackId)]; const image = getImagePath(leaderA.teamId, code, "champion"); const title = generateTitle({ driver_name: leaderA.name, circuit: raceInfo.circuit, country: raceInfo.country, adjective: raceInfo.adjective, season_year: currentSeason }, 9); out.push({ id: newsId, type: "world_champion", title, date: finalNewsDateExcel, image, overlay: null, text: null, data: { raceId: race.id, season_year: currentSeason, driver_id: leaderA.driverId, driver_team_id: leaderA.teamId, driver_name: leaderA.name, driver_points: leaderA.points, rival_driver_id: rivalA.driverId, rival_driver_name: rivalA.name, rival_points: rivalA.points, circuit_name: raceInfo.circuit, country_name: raceInfo.country, adjective: raceInfo.adjective } }); } } } } // Avanza el flag para el próximo i wasAlreadyChampionBeforeThisRace = alreadyChampionBeforeNext; } return out; } export function getCircuitInfo(raceId) { const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)]; if (!code) return "Unknown Circuit"; return countries_data[code] || code; } export function getCustomNewsOptions() { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const currentDay = Number(daySeason?.[0] ?? 0); const seasonYear = Number(daySeason?.[1] ?? 0); const races = queryDB( `SELECT RaceID, TrackID, Day, State, WeekendType FROM Races WHERE SeasonID = ? ORDER BY RaceID`, [seasonYear], 'allRows' ).map(([raceId, trackId, day, state, weekendType]) => { const code = races_names?.[Number(trackId)]; const info = code ? countries_data?.[code] : null; const jsDate = excelToDate(Number(day)); const iso = new Date(jsDate.getFullYear(), jsDate.getMonth(), jsDate.getDate()).toISOString().slice(0, 10); return { id: Number(raceId), trackId: Number(trackId), day: Number(day), state: Number(state), weekendType: Number(weekendType), code: code || null, label: info?.adjective ? `${seasonYear} ${info.adjective} GP` : (code ? `${seasonYear} ${code} GP` : `Race ${raceId}`), dateIso: iso }; }); const isCreateATeam = !!getGlobals()?.isCreateATeam; const teamIds = isCreateATeam ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const teams = teamIds.map(id => ({ id, name: combined_dict[id] || `Team ${id}` })); const drivers = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.ContractType = 0 AND con.PosInTeam <= 2 AND bas.FirstName != 'Placeholder'`, [], 'allRows' ).map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { id: Number(driverId), name: news_insert_space(nameFormatted), teamId: Number(teamId), teamName: combined_dict[teamId] || `Team ${teamId}` }; }); const allDrivers = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID LEFT JOIN Staff_Contracts con ON bas.StaffID = con.StaffID AND con.ContractType = 0 LEFT JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE bas.FirstName != 'Placeholder' AND IFNULL(gd.Retired, 0) = 0`, [], 'allRows' ).map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); const teamNum = Number(teamId) || 0; return { id: Number(driverId), name: news_insert_space(nameFormatted), teamId: teamNum || null, teamName: teamNum ? (combined_dict[teamNum] || `Team ${teamNum}`) : "Free Agent" }; }); const engines = (fetchEngines()?.[0] || []).map(([engineId, _, engineName]) => ({ id: Number(engineId), name: String(engineName || `Engine ${engineId}`) })); return { seasonYear, currentDay, teams, races, drivers, allDrivers, engines }; } function getOfficialF1DriverIdsForCustomNews(teamId = null) { const params = []; let sql = ` SELECT con.StaffID FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.ContractType = 0 AND con.PosInTeam <= 2 `; if (teamId != null) { sql += ` AND con.TeamID = ?`; params.push(Number(teamId)); } return (queryDB(sql, params, 'allRows') || []).map(row => Number(row[0])); } function assertOfficialF1DriverForCustomNews(driverId, label = "Driver") { const exists = queryDB( `SELECT 1 FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.StaffID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2 LIMIT 1`, [Number(driverId)], 'singleValue' ); if (!exists) { throw new Error(`${label} must be a current Formula 1 driver`); } } function isOfficialF1DriverForCustomNews(driverId) { const exists = queryDB( `SELECT 1 FROM Staff_Contracts con JOIN Staff_DriverData dri ON con.StaffID = dri.StaffID WHERE con.StaffID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2 LIMIT 1`, [Number(driverId)], 'singleValue' ); return !!exists; } function assertActiveDriverForCustomNews(driverId, label = "Driver") { const exists = queryDB( `SELECT 1 FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID LEFT JOIN Staff_GameData gd ON bas.StaffID = gd.StaffID WHERE bas.StaffID = ? AND bas.FirstName != 'Placeholder' AND IFNULL(gd.Retired, 0) = 0 LIMIT 1`, [Number(driverId)], 'singleValue' ); if (!exists) { throw new Error(`${label} must be an active driver`); } } function assertCompletedRaceForCustomNews(raceId, label = "Race") { const raceState = queryDB(`SELECT State FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); if (Number(raceState) !== 2) { throw new Error(`${label} must already have taken place`); } } export function getRaceDriversForCustomNews(raceId) { const results = getOneRaceResults(raceId) || []; return results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { driverId: Number(driverId), name: news_insert_space(nameFormatted), teamId: Number(teamId), teamName: combined_dict[teamId] || `Team ${teamId}`, pos: Number(row[4]) }; }); } function isoToExcelDay(iso) { if (!iso || typeof iso !== "string") return null; const m = iso.match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!m) return null; const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])); return dateToExcel(d); } function getDriverAndTeamForCustomNews(driverId) { const row = queryDB( `SELECT bas.FirstName, bas.LastName, bas.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID LEFT JOIN Staff_Contracts con ON bas.StaffID = con.StaffID AND con.ContractType = 0 WHERE bas.StaffID = ? LIMIT 1`, [driverId], 'singleRow' ); const [nameFormatted, id, teamId] = formatNamesSimple(row || ["Unknown", "Driver", driverId, 0]); return { driverId: Number(id), name: news_insert_space(nameFormatted), teamId: Number(teamId || 0), teamName: Number(teamId || 0) > 0 ? (combined_dict[teamId] || `Team ${teamId}`) : "Free Agent" }; } function renderNormalTitleTemplate(data, new_type, templateIndex) { const templateObj = newsTitleTemplates.find(t => t.new_type === new_type); const titles = Array.isArray(templateObj?.titles) ? templateObj.titles : []; const tpl = titles[templateIndex]; if (!tpl) return null; const prepared = { ...(data || {}) }; if (prepared.raceId) { const raceInfo = getCircuitInfo(prepared.raceId); prepared.circuit = raceInfo?.circuit; prepared.country = raceInfo?.country; prepared.adjective = raceInfo?.adjective; } const paramMap = getParamMap(prepared); return tpl.replace(/{{\s*(\w+)\s*}}/g, (_, key) => paramMap?.[new_type]?.[key] || ''); } function renderTurningPointTitleTemplate(data, new_type, turningPointType, templateIndex) { const templateObj = turningPointsTitleTemplates.find(t => t.new_type === new_type); let titles = []; if (turningPointType === "positive") titles = templateObj?.positive_titles || []; else if (turningPointType === "negative") titles = templateObj?.negative_titles || []; else titles = templateObj?.turning_titles || []; const tpl = titles[templateIndex]; if (!tpl) return null; const paramMap = getParamMap(data || {}); return tpl.replace(/{{\s*(\w+)\s*}}/g, (_, key) => paramMap?.[new_type]?.[key] || ''); } function assertCustomTurningPointPayload(type, data) { if (!type?.startsWith("turning_point_")) return; if (!data || typeof data !== "object") { throw new Error(`Incomplete turning point data for ${type}`); } const requireNumber = (value, label) => { if (!Number.isFinite(Number(value))) { throw new Error(`Missing ${label} for ${type}`); } }; const requireText = (value, label) => { if (typeof value !== "string" || !value.trim()) { throw new Error(`Missing ${label} for ${type}`); } }; const requireObject = (value, label) => { if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length === 0) { throw new Error(`Missing ${label} for ${type}`); } }; const requireArray = (value, label) => { if (!Array.isArray(value) || value.length === 0) { throw new Error(`Missing ${label} for ${type}`); } }; if (type === "turning_point_technical_directive") { requireNumber(data.componentId, "componentId"); requireObject(data.effectOnEachteam, "effectOnEachteam"); return; } if (type === "turning_point_transfer") { requireNumber(data.teamId, "teamId"); requireNumber(data.driver_out?.id, "driver_out.id"); requireNumber(data.driver_in?.id, "driver_in.id"); return; } if (type === "turning_point_investment") { requireNumber(data.teamId, "teamId"); requireNumber(data.investmentAmount, "investmentAmount"); requireNumber(data.investmentShare, "investmentShare"); requireText(data.country, "country"); return; } if (type === "turning_point_dsq") { requireNumber(data.race_id, "race_id"); requireNumber(data.teamId, "teamId"); requireText(data.component, "component"); return; } if (type === "turning_point_race_substitution") { requireNumber(data.raceId, "raceId"); requireNumber(data.newRaceTrackId, "newRaceTrackId"); requireNumber(data.newRaceDay, "newRaceDay"); return; } if (type === "turning_point_injury") { requireNumber(data.driver_affected?.id, "driver_affected.id"); requireNumber(data.condition?.end_date, "condition.end_date"); requireNumber(data.reserve_driver?.id, "reserve_driver.id"); requireArray(data.condition?.races_affected, "condition.races_affected"); return; } if (type === "turning_point_engine_regulation") { requireObject(data.engineData, "engineData"); return; } if (type === "turning_point_young_drivers") { requireArray(data.prospects, "prospects"); data.prospects.forEach((prospect, index) => { requireNumber(prospect?.driverId, `prospects[${index}].driverId`); }); return; } if (type === "turning_point_aduo") { requireNumber(data.quarter, "quarter"); requireArray(data.engineImprovements, "engineImprovements"); data.engineImprovements.forEach((engineChange, index) => { requireNumber(engineChange?.engineId, `engineImprovements[${index}].engineId`); requireText(engineChange?.name, `engineImprovements[${index}].name`); requireObject(engineChange?.improvements, `engineImprovements[${index}].improvements`); }); } } export function createCustomNewsEntry(input = {}) { const { type, title, titleTemplateIndex, dateIso, params } = input || {}; if (!type || typeof type !== "string") { throw new Error("Missing custom news type"); } const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonYear = Number(daySeason?.[1] ?? 0); const currentDay = Number(daySeason?.[0] ?? 0); const now = Date.now(); const id = type === "custom_new" ? `custom_new_${now}` : `custom_${type}_${now}`; const stableKey = id; const selectedTemplateIndex = titleTemplateIndex != null ? Number(titleTemplateIndex) : null; const dateFromIso = isoToExcelDay(dateIso); let date = dateFromIso != null ? dateFromIso : currentDay; const currentMonth = excelToDate(date).getMonth() + 1; let data = null; let overlay = null; let image = null; let finalTitle = (typeof title === "string" && title.trim()) ? title.trim() : null; if (type === "race_result") { const raceId = Number(params?.raceId); if (!raceId || raceId <= 0) throw new Error("Missing raceId"); assertCompletedRaceForCustomNews(raceId); const results = getOneRaceResults(raceId); if (!results?.length) throw new Error("No race results found for that race"); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4] }; }); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)]; data = { raceId, first: formatted[0].name, second: formatted[1].name, third: formatted[2].name, firstTeam: formatted[0].teamId, secondTeam: formatted[1].teamId, thirdTeam: formatted[2].teamId, trackId: trackId, seasonYear: seasonYear, }; if (dateFromIso == null) { const d = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); date = Number(d); } if (!finalTitle) { finalTitle = renderNormalTitleTemplate({ raceId, seasonYear, winnerName: formatted[0].name }, 2, selectedTemplateIndex) || generateTitle({ raceId, seasonYear, winnerName: formatted[0].name }, 2); } overlay = "race-overlay"; image = getImagePath(formatted[0].teamId, code, "raceQuali"); } else if (type === "quali_result") { const raceId = Number(params?.raceId); if (!raceId || raceId <= 0) throw new Error("Missing raceId"); assertCompletedRaceForCustomNews(raceId); const results = getOneQualifyingResults(raceId); if (!results?.length) throw new Error("No qualifying results found for that race"); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4], fastestLap: row[5] }; }); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)]; data = { raceId, first: formatted[0].name, second: formatted[1].name, third: formatted[2].name, firstTeam: formatted[0].teamId, secondTeam: formatted[1].teamId, thirdTeam: formatted[2].teamId, trackId: trackId, seasonYear: seasonYear, }; if (dateFromIso == null) { const d = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue') - 1; date = Number(d); } if (!finalTitle) { finalTitle = renderNormalTitleTemplate({ raceId, seasonYear, pole_driver: formatted[0].name }, 1, selectedTemplateIndex) || generateTitle({ raceId, seasonYear, pole_driver: formatted[0].name }, 1); } overlay = "quali-overlay"; image = getImagePath(formatted[0].teamId, `${code}_car`, "raceQuali"); } else if (type === "race_reaction") { const raceId = Number(params?.raceId); if (!raceId || raceId <= 0) throw new Error("Missing raceId"); assertCompletedRaceForCustomNews(raceId); const results = getOneRaceResults(raceId); if (!results?.length) throw new Error("No race results found for that race"); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId: Number(driverId), teamId: Number(teamId), teamName: combined_dict[teamId], pos: Number(row[4]), rating: getDriverOverall(driverId) }; }); const pickById = (idVal) => formatted.find(d => Number(d.driverId) === Number(idVal)) || null; const chosenHappy = params?.happyDriverId ? pickById(params.happyDriverId) : null; const chosenUnhappy = params?.unhappyDriverId ? pickById(params.unhappyDriverId) : null; const unhappyDrivers = formatted.filter(r => r.rating >= 88 && r.pos > 6); const happyDrivers = formatted.filter(r => r.pos <= 4); const randomUnHappyDriver = chosenUnhappy || randomPick(unhappyDrivers.length ? unhappyDrivers : formatted); const randomHappyDriver = chosenHappy || randomPick(happyDrivers.length ? happyDrivers : formatted); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)].toLowerCase(); data = { raceId, allHappyDrivers: happyDrivers, allUnhappyDrivers: unhappyDrivers, randomHappyDriver, happyTeam: randomHappyDriver.teamName, unhappyTeam: randomUnHappyDriver.teamName, randomUnHappyDriver, seasonYear, trackId: trackId }; if (!finalTitle) { finalTitle = renderNormalTitleTemplate(data, 16, selectedTemplateIndex) || generateTitle(data, 16); } if (dateFromIso == null) { const d = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); date = Number(d) + 1; } let driverTeamIdInTitle = null; if (finalTitle.includes(randomUnHappyDriver.name)) driverTeamIdInTitle = randomUnHappyDriver.teamId; else if (finalTitle.includes(randomHappyDriver.name)) driverTeamIdInTitle = randomHappyDriver.teamId; if (driverTeamIdInTitle != null) data.driverTeamIdInTitle = driverTeamIdInTitle; overlay = "reaction-overlay"; image = getImagePath(driverTeamIdInTitle, code, "reaction"); } else if (type === "fake_transfer") { const driverId = Number(params?.driverId); if (!driverId || driverId <= 0) throw new Error("Missing driverId"); assertOfficialF1DriverForCustomNews(driverId); const d = getDriverAndTeamForCustomNews(driverId); data = { drivers: [{ name: d.name, driverId: d.driverId, team: d.teamName, teamId: d.teamId }] }; if (!finalTitle) { finalTitle = renderNormalTitleTemplate({ driver1: d.name, team1: d.teamName }, 7, selectedTemplateIndex) || generateTitle({ driver1: d.name, team1: d.teamName }, 7); } overlay = "fake-transfer-overlay"; image = getImagePath(d.teamId, d.driverId, "transfer"); } else if (type === "big_transfer" || type === "massive_exit" || type === "massive_signing") { const driverId = Number(params?.driverId); const fromTeamId = Number(params?.fromTeamId); const toTeamId = Number(params?.toTeamId); const salary = params?.salary != null ? Number(params.salary) : null; const endSeason = params?.endSeason != null ? Number(params.endSeason) : null; if (!driverId || driverId <= 0) throw new Error("Missing driverId"); if (!fromTeamId || fromTeamId <= 0) throw new Error("Missing fromTeamId"); if (!toTeamId || toTeamId <= 0) throw new Error("Missing toTeamId"); assertOfficialF1DriverForCustomNews(driverId); const d = getDriverAndTeamForCustomNews(driverId); const team1 = combined_dict[fromTeamId] || `Team ${fromTeamId}`; const team2 = combined_dict[toTeamId] || `Team ${toTeamId}`; data = { driver1: d.name, driverId: d.driverId, team1: team1, team2: team2, team1Id: fromTeamId, team2Id: toTeamId, salary: salary, endSeason: endSeason, season_year: seasonYear }; if (!finalTitle) { const titleType = type === "big_transfer" ? 6 : (type === "massive_exit" ? 17 : 18); finalTitle = renderNormalTitleTemplate(data, titleType, selectedTemplateIndex) || generateTitle(data, titleType); } overlay = type === "massive_exit" ? "massive-exit-overlay" : "massive-signing-overlay"; const imageTeamId = type === "massive_exit" ? fromTeamId : toTeamId; image = getImagePath(imageTeamId, d.driverId, "transfer"); } else if (type === "contract_renewal") { const driverId = Number(params?.driverId); const renewalTeamId = Number(params?.renewalTeamId); const currentTeamId = Number(params?.currentTeamId); const salary = params?.salary != null ? Number(params.salary) : null; const endSeason = params?.endSeason != null ? Number(params.endSeason) : null; if (!driverId || driverId <= 0) throw new Error("Missing driverId"); if (!renewalTeamId || renewalTeamId <= 0) throw new Error("Missing renewalTeamId"); if (!currentTeamId || currentTeamId <= 0) throw new Error("Missing currentTeamId"); assertOfficialF1DriverForCustomNews(driverId); const d = getDriverAndTeamForCustomNews(driverId); data = { driver1: d.name, driverId: d.driverId, team1: combined_dict[renewalTeamId] || `Team ${renewalTeamId}`, team2: combined_dict[currentTeamId] || `Team ${currentTeamId}`, team1Id: renewalTeamId, team2Id: currentTeamId, salary: salary, endSeason: endSeason }; if (!finalTitle) { finalTitle = renderNormalTitleTemplate(data, 10, selectedTemplateIndex) || generateTitle(data, 10); } overlay = "contract-renewal-overlay"; image = getImagePath(renewalTeamId, d.driverId, "transfer"); } else if (type === "silly_season_rumors") { const items = Array.isArray(params?.drivers) ? params.drivers : []; const drivers = items.slice(0, 6).map(it => { const driverId = Number(it?.driverId); if (!driverId || driverId <= 0) return null; assertOfficialF1DriverForCustomNews(driverId); const d = getDriverAndTeamForCustomNews(driverId); const potentialTeam = it?.potentialTeam != null ? Number(it.potentialTeam) : null; const salary = it?.salary != null ? Number(it.salary) : null; const endSeason = it?.endSeason != null ? Number(it.endSeason) : null; return { driverId: d.driverId, name: d.name, team: d.teamName, teamId: d.teamId, potentialTeam, potentialSalary: salary, potentialYearEnd: endSeason }; }).filter(Boolean); if (drivers.length < 3) throw new Error("Pick at least 3 drivers"); if (!finalTitle) { const titleData = { driver1: drivers[0].name, driver2: drivers[1].name, driver3: drivers[2].name, team1: drivers[0].potentialTeam ? (combined_dict[drivers[0].potentialTeam] || "") : "", team2: drivers[1].potentialTeam ? (combined_dict[drivers[1].potentialTeam] || "") : "", team3: drivers[2].potentialTeam ? (combined_dict[drivers[2].potentialTeam] || "") : "", season: seasonYear }; finalTitle = renderNormalTitleTemplate(titleData, 4, selectedTemplateIndex) || generateTitle(titleData, 4); } if (dateFromIso == null) { date = dateToExcel(new Date(seasonYear, 7, 10)); } data = { drivers }; overlay = "silly-season-overlay"; image = getImagePath(drivers[0].teamId, drivers[0].driverId, "transfer_generic"); } else if (type === "team_comparison") { const teamId = Number(params?.teamId); const compType = params?.compType === "bad" ? "bad" : "good"; const drop = params?.drop != null ? Number(params.drop) : 0; if (!teamId || teamId <= 0) throw new Error("Missing teamId"); data = { team: { teamId, drop }, season: seasonYear, compType }; if (!finalTitle) { const titleType = compType === "good" ? 12 : 11; const titleData = { teamId: combined_dict[teamId] || `Team ${teamId}`, season: seasonYear }; finalTitle = renderNormalTitleTemplate(titleData, titleType, selectedTemplateIndex) || generateTitle(titleData, titleType); } image = getImagePath(teamId, teamId, "teamComparison"); overlay = null; } else if (type === "driver_comparison") { const teamId = Number(params?.teamId); const driver1Id = Number(params?.driver1Id); const driver2Id = Number(params?.driver2Id); if (!teamId || teamId <= 0) throw new Error("Missing teamId"); if (!driver1Id || driver1Id <= 0) throw new Error("Missing driver1Id"); if (!driver2Id || driver2Id <= 0) throw new Error("Missing driver2Id"); assertOfficialF1DriverForCustomNews(driver1Id, "Driver 1"); assertOfficialF1DriverForCustomNews(driver2Id, "Driver 2"); const d1 = getDriverAndTeamForCustomNews(driver1Id); const d2 = getDriverAndTeamForCustomNews(driver2Id); const teamName = combined_dict[teamId] || `Team ${teamId}`; data = { teamId, teamName, drivers: [{ name: d1.name, driverId: d1.driverId }, { name: d2.name, driverId: d2.driverId }], season: seasonYear }; if (!finalTitle) { finalTitle = renderNormalTitleTemplate({ team: teamName, driver1: d1.name, driver2: d2.name }, 13, selectedTemplateIndex) || generateTitle({ team: teamName, driver1: d1.name, driver2: d2.name }, 13); } overlay = "driver-comparison-overlay"; image = null; } else if (type === "season_review") { const part = Number(params?.part); if (![1, 2, 3].includes(part)) throw new Error("Invalid season review part"); const nRaces = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [seasonYear], 'singleValue'); const racesInterval = nRaces / 3; const firstRaceSeasonId = queryDB(`SELECT MIN(RaceID) FROM Races WHERE SeasonID = ?`, [seasonYear], 'singleValue'); const seasonResults = fetchSeasonResults(seasonYear); const raceIdInPoint = firstRaceSeasonId + Math.floor(racesInterval * part) - 1; const { driverStandings, teamStandings } = rebuildStandingsUntil(seasonResults, raceIdInPoint); const firstDriver = driverStandings[0]; const secondDriver = driverStandings[1]; const firstTeam = teamStandings[0]; const secondTeam = teamStandings[1]; data = { season: seasonYear, part, firstDriver, secondDriver, firstTeam, secondTeam }; if (!finalTitle) { const titleId = part === 3 ? 15 : 14; const titleData = { season: seasonYear, part, driver1: firstDriver?.name, driver2: secondDriver?.name }; finalTitle = renderNormalTitleTemplate(titleData, titleId, selectedTemplateIndex) || generateTitle(titleData, titleId); } image = getImagePath(firstTeam ? firstTeam.id : 1, firstDriver ? firstDriver.id : 1, "season_review"); overlay = null; } else if (type === "potential_champion" || type === "world_champion") { const raceId = Number(params?.raceId); if (!raceId || raceId <= 0) throw new Error("Missing raceId"); assertCompletedRaceForCustomNews(raceId); const seasonResults = fetchSeasonResults(seasonYear, true); const standings = type === "potential_champion" ? rebuildStandingsUntil(seasonResults, raceId - 1, true) : rebuildStandingsUntil(seasonResults, raceId, true); const leader = standings?.driverStandings?.[0]; const rival = standings?.driverStandings?.[1]; if (!leader || !rival) throw new Error("Not enough standings data for this race"); const raceInfo = getCircuitInfo(raceId); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); const code = races_names[Number(trackId)]; data = { raceId, season_year: seasonYear, driver_id: leader.driverId, driver_team_id: leader.teamId, driver_name: leader.name, driver_points: leader.points, rival_driver_id: rival.driverId, rival_driver_name: rival.name, rival_points: rival.points, circuit_name: raceInfo.circuit, country_name: raceInfo.country, adjective: raceInfo.adjective }; if (!finalTitle) { const titleData = { driver_name: leader.name, circuit: raceInfo.circuit, country: raceInfo.country, adjective: raceInfo.adjective, season_year: seasonYear }; const titleType = type === "potential_champion" ? 8 : 9; finalTitle = renderNormalTitleTemplate(titleData, titleType, selectedTemplateIndex) || generateTitle(titleData, titleType); } overlay = null; image = getImagePath(leader.teamId, (type === "potential_champion" ? code?.toLowerCase() : code), "champion"); if (dateFromIso == null) { const baseDay = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); date = type === "potential_champion" ? (Number(baseDay) - 2) : (Number(baseDay) + 1); } } else if (type === "next_season_grid") { const globals = getGlobals(); let teamIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; if (globals?.isCreateATeam) teamIds.push(32); let teamsDict = {}; teamIds.forEach(teamId => { const teamName = combined_dict[teamId] || "Unknown Team"; let teamInfo = { name: teamName, teamId: teamId, driversNextSeason: [], driversThisSeason: [] }; const driversThisSeason = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID, con.ContractType FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.TeamID = ? AND con.PosInTeam <= 2 AND con.ContractType = 0`, [teamId], 'allRows' ); const driversNextSeason = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID, con.ContractType FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.TeamID = ? AND con.PosInTeam <= 2 AND con.ContractType IN (0,3)`, [teamId], 'allRows' ); driversNextSeason.forEach(d => { const name = formatNamesSimple(d); const contractType = d[4]; teamInfo.driversNextSeason.push({ name: news_insert_space(name[0]), driverId: name[1], isForNextSeason: contractType === 3 }); }); driversThisSeason.forEach(d => { const name = formatNamesSimple(d); teamInfo.driversThisSeason.push({ name: news_insert_space(name[0]), driverId: name[1] }); }); teamsDict[teamId] = teamInfo; }); const nextYear = seasonYear + 1; data = { season_year: nextYear, teams: teamsDict }; if (!finalTitle) finalTitle = renderNormalTitleTemplate({ season_year: nextYear }, 19, selectedTemplateIndex) || generateTitle({ season_year: nextYear }, 19); overlay = "next-season-grid"; image = getImagePath(null, null, "grid"); if (dateFromIso == null) { date = dateToExcel(new Date(seasonYear, 11, 15)); } } else if (type === "feeder_series_review") { const f2 = queryDB( `SELECT bas.FirstName, bas.LastName, rds.DriverID, 1 FROM Races_DriverStandings rds JOIN Staff_BasicData bas ON rds.DriverID = bas.StaffID WHERE rds.SeasonID = ? AND rds.RaceFormula = 2 AND rds.Position = 1 LIMIT 1`, [seasonYear], "singleRow" ); const f3 = queryDB( `SELECT bas.FirstName, bas.LastName, rds.DriverID, 1 FROM Races_DriverStandings rds JOIN Staff_BasicData bas ON rds.DriverID = bas.StaffID WHERE rds.SeasonID = ? AND rds.RaceFormula = 3 AND rds.Position = 1 LIMIT 1`, [seasonYear], "singleRow" ); const [f2Name, f2Id] = formatNamesSimple(f2 || ["Unknown", "Driver", 0]); const [f3Name, f3Id] = formatNamesSimple(f3 || ["Unknown", "Driver", 0]); data = { f2_champion: { name: news_insert_space(f2Name), driverId: f2Id }, f3_champion: { name: news_insert_space(f3Name), driverId: f3Id }, season_year: seasonYear }; if (!finalTitle) { const titleData = { season_year: seasonYear, f2_champion: data.f2_champion.name, f3_champion: data.f3_champion.name }; finalTitle = renderNormalTitleTemplate(titleData, 20, selectedTemplateIndex) || generateTitle(titleData, 20); } overlay = null; image = getImagePath(null, null, "young"); } else if (type === "turning_point_technical_directive") { const componentId = Number(params?.componentId); const reason = String(params?.reason || "").trim() || "improve safety"; const requestedEffectMode = String(params?.effectMode || "").trim().toLowerCase(); const effectMode = ["random", "spread", "compact"].includes(requestedEffectMode) ? requestedEffectMode : null; const requestedEffectAmount = Number(params?.effectAmount); if (![3, 4, 5, 6, 7, 8].includes(componentId)) throw new Error("Missing componentId"); const globals = getGlobals(); const teamIds = globals?.isCreateATeam ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const effectOnEachteam = {}; const capsByPart = { 3: 7.5, 4: 4, 5: 4, 6: 4, 7: 7, 8: 5 }; const cap = requestedEffectAmount > 0 ? requestedEffectAmount : (capsByPart[componentId] || 5); const modeRoll = Math.random(); const resolvedEffectMode = effectMode || (modeRoll < 0.30 ? "random" : modeRoll < 0.40 ? "compact" : "spread"); const clamp = (v, min, max) => Math.max(min, Math.min(max, v)); if (resolvedEffectMode === "random") { for (const teamId of teamIds) { effectOnEachteam[teamId] = { performanceGainLoss: ((Math.random() * 2 * cap) - cap).toFixed(2), teamName: combined_dict[teamId] || `Team ${teamId}`, teamId }; } } else { const performance = getPerformanceAllTeams(date, null, globals?.isCreateATeam); const championship = queryDB( `SELECT TeamID, Points FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = 1`, [seasonYear], 'allRows' ); const constructorsPoints = {}; for (const row of championship) { constructorsPoints[Number(row[0])] = Number(row[1]) || 0; } const vals = teamIds.map(id => Number(performance?.[id] || 0)); const mean = vals.length ? vals.reduce((a, b) => a + b, 0) / vals.length : 0; const maxAbs = Math.max(...vals.map(v => Math.abs(v - mean)), 1); const maxPts = Math.max(...teamIds.map(id => constructorsPoints[id] || 0), 1); const effectiveCap = !effectMode && resolvedEffectMode === "spread" ? (cap * 1.45) : cap; const direction = resolvedEffectMode === "compact" ? -1 : 1; const rawEffects = teamIds.map((teamId) => { const normalizedPerformance = (Number(performance?.[teamId] || mean) - mean) / maxAbs; const normalizedPoints = ((constructorsPoints[teamId] || 0) / maxPts) - 0.5; let effect = (normalizedPerformance * 0.8 + normalizedPoints * 0.2) * effectiveCap * direction; effect += (Math.random() * 0.3) - 0.15; return { teamId, effect }; }); const averageEffect = rawEffects.length ? rawEffects.reduce((sum, item) => sum + item.effect, 0) / rawEffects.length : 0; for (const { teamId, effect } of rawEffects) { effectOnEachteam[teamId] = { performanceGainLoss: clamp(effect - averageEffect, -effectiveCap, effectiveCap).toFixed(2), teamName: combined_dict[teamId] || `Team ${teamId}`, teamId }; } } data = { component: String(part_full_names[componentId] || "Unknown component").toLowerCase(), componentId, effectOnEachteam, effectMode: resolvedEffectMode, effectAmount: cap, month: currentMonth, reason, season: seasonYear }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 100, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 100, "original"); } image = getImagePath(null, componentId, "technical"); } else if (type === "turning_point_transfer") { const teamId = Number(params?.teamId); const driverOutId = Number(params?.driverOutId); const driverInId = Number(params?.driverInId); const reserveDriverId = params?.reserveDriverId != null ? Number(params.reserveDriverId) : null; if (!teamId || teamId <= 0) throw new Error("Missing teamId"); if (!driverOutId || driverOutId <= 0) throw new Error("Missing driverOutId"); if (!driverInId || driverInId <= 0) throw new Error("Missing driverInId"); assertOfficialF1DriverForCustomNews(driverOutId, "Driver out"); assertActiveDriverForCustomNews(driverInId, "Driver in"); if (reserveDriverId > 0) assertActiveDriverForCustomNews(reserveDriverId, "Substitute driver"); const selectedTeamDriverIds = new Set(getOfficialF1DriverIdsForCustomNews(teamId)); if (!selectedTeamDriverIds.has(driverOutId)) { throw new Error("Driver out must belong to the selected team"); } if (selectedTeamDriverIds.has(driverInId)) { throw new Error("Driver in must come from outside the selected team"); } const driverInHasOfficialSeat = isOfficialF1DriverForCustomNews(driverInId); if (!driverInHasOfficialSeat && reserveDriverId > 0) { throw new Error("A substitute driver is only allowed when driver in comes from an official F1 seat"); } if (reserveDriverId > 0) { if (isOfficialF1DriverForCustomNews(reserveDriverId)) { throw new Error("Substitute driver cannot come from another official F1 seat"); } if (selectedTeamDriverIds.has(reserveDriverId)) { throw new Error("Substitute driver must come from outside the selected team"); } } const driverOut = getDriverAndTeamForCustomNews(driverOutId); const driverIn = getDriverAndTeamForCustomNews(driverInId); const reserveDriver = reserveDriverId > 0 ? getDriverAndTeamForCustomNews(reserveDriverId) : null; data = { team: combined_dict[teamId] || `Team ${teamId}`, teamId, driver_out: { id: driverOut.driverId, name: driverOut.name, teamId }, driver_in: { id: driverIn.driverId, name: driverIn.name, teamId: driverIn.teamId || null }, driver_substitute: reserveDriver ? { id: reserveDriver.driverId, name: reserveDriver.name, teamId: reserveDriver.teamId || null } : null, month: currentMonth, season: seasonYear }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 101, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 101, "original"); } image = getImagePath(null, driverOut.driverId, "transfer"); } else if (type === "turning_point_investment") { const teamId = Number(params?.teamId); const country = String(params?.country || "").trim(); const investmentAmount = Number(params?.investmentAmount); const investmentShare = Number(params?.investmentShare); if (!teamId || teamId <= 0) throw new Error("Missing teamId"); if (!country) throw new Error("Missing country"); if (!investmentAmount || investmentAmount <= 0) throw new Error("Missing investmentAmount"); if (!investmentShare || investmentShare <= 0) throw new Error("Missing investmentShare"); data = { country, teamId, teamName: combined_dict[teamId] || `Team ${teamId}`, investmentAmount, investmentShare, season: seasonYear, month: currentMonth, }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 102, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 102, "original"); } image = getImagePath(null, country.slice(0, 3).toLowerCase(), "investment"); } else if (type === "turning_point_dsq") { const raceId = Number(params?.raceId); const teamId = Number(params?.teamId); const component = String(params?.component || "").trim(); if (!raceId || raceId <= 0) throw new Error("Missing raceId"); if (!teamId || teamId <= 0) throw new Error("Missing teamId"); if (!component) throw new Error("Missing component"); assertCompletedRaceForCustomNews(raceId); const driversInRace = queryDB( `SELECT bas.FirstName, bas.LastName, res.TeamID, res.Points, bas.StaffID, res.FinishingPos FROM Races_Results res JOIN Staff_BasicData bas ON res.DriverID = bas.StaffID WHERE res.RaceID = ? AND res.TeamID = ?`, [raceId, teamId], 'allRows' ); if (!driversInRace.length) throw new Error("No drivers found for that team in the selected race"); const formatRaceDriver = (row) => { const [nameFormatted] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), points: Number(row[3]) || 0, position: Number(row[5]) || 0, driverId: Number(row[4]) || 0 }; }; const raceInfo = getCircuitInfo(raceId); data = { team: combined_dict[teamId] || `Team ${teamId}`, adjective: raceInfo?.adjective || "", circuit: raceInfo?.circuit || "", country: raceInfo?.country || "", race_id: raceId, component, teamId, currentSeason: seasonYear, driver_1: formatRaceDriver(driversInRace[0]), driver_2: driversInRace[1] ? formatRaceDriver(driversInRace[1]) : formatRaceDriver(driversInRace[0]) }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 103, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 103, "original"); } image = getImagePath(null, null, "dsq"); if (dateFromIso == null) { const raceDay = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); date = Number(raceDay) + 2; } } else if (type === "turning_point_race_substitution") { const raceId = Number(params?.raceId); const newRaceTrackId = Number(params?.newRaceTrackId); const reason = String(params?.reason || "").trim() || "calendar restructuring"; if (!raceId || raceId <= 0) throw new Error("Missing raceId"); if (!newRaceTrackId || newRaceTrackId <= 0) throw new Error("Missing newRaceTrackId"); const raceRow = queryDB(`SELECT TrackID, Day FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); if (!raceRow) throw new Error("Selected race was not found"); const originalTrackId = Number(raceRow[0]); data = { originalCountry: countries_data[races_names[originalTrackId]]?.adjective || "Unknown Country", substituteCountry: countries_data[races_names[newRaceTrackId]]?.country || "Unknown Country", reason, originalTrackId, newRaceTrackId, newRaceDay: Number(raceRow[1]), raceId, month: currentMonth, season: seasonYear, typeOfSubstitution: originalTrackId === newRaceTrackId ? "same_track" : "different_race" }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 105, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 105, "original"); } image = getImagePath(null, String(races_names[originalTrackId] || "").toLowerCase(), "race_substitution"); } else if (type === "turning_point_injury") { const driverId = Number(params?.driverId); const reserveDriverId = params?.reserveDriverId != null ? Number(params.reserveDriverId) : null; const conditionText = String(params?.condition || "").trim() || "an illness"; const reason = String(params?.reason || "").trim() || "a medical issue"; const racesAffectedCount = Math.max(1, Number(params?.racesAffected) || 1); if (!driverId || driverId <= 0) throw new Error("Missing driverId"); assertOfficialF1DriverForCustomNews(driverId, "Affected driver"); if (reserveDriverId > 0) { assertActiveDriverForCustomNews(reserveDriverId, "Reserve driver"); if (isOfficialF1DriverForCustomNews(reserveDriverId)) { throw new Error("Reserve driver cannot come from another official F1 seat"); } } const driver = getDriverAndTeamForCustomNews(driverId); const reserveDriver = reserveDriverId > 0 ? getDriverAndTeamForCustomNews(reserveDriverId) : null; const racesAffected = queryDB( `SELECT RaceID, Day, TrackID FROM Races WHERE SeasonID = ? AND Day >= ? ORDER BY Day ASC LIMIT ?`, [seasonYear, date, racesAffectedCount], 'allRows' ); const lastRaceDay = racesAffected.length ? Number(racesAffected[racesAffected.length - 1][1]) : date; const expectedReturnRace = queryDB( `SELECT RaceID, TrackID FROM Races WHERE SeasonID = ? AND Day > ? ORDER BY Day ASC LIMIT 1`, [seasonYear, lastRaceDay], 'singleRow' ); data = { team: driver.teamName, teamId: driver.teamId, driver_affected: { id: driver.driverId, name: driver.name, teamId: driver.teamId }, condition: { type: "custom", condition: conditionText, reason, start_date: date, end_date: lastRaceDay, next_race_min_enforced: true, races_affected: racesAffected.map(([affectedRaceId, affectedDay, affectedTrackId]) => ({ raceId: Number(affectedRaceId), country: countries_data[races_names[Number(affectedTrackId)]]?.country, day: Number(affectedDay) })), expectedReturnRaceId: expectedReturnRace ? Number(expectedReturnRace[0]) : null, expectedReturnCountry: expectedReturnRace ? (countries_data[races_names[Number(expectedReturnRace[1])]]?.country || "") : "" }, reserve_driver: reserveDriver ? { id: reserveDriver.driverId, name: reserveDriver.name, teamId: reserveDriver.teamId || null, isFreeAgent: !reserveDriver.teamId, futureTeamId: driver.teamId } : null, month: currentMonth, season: seasonYear }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 106, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 106, "original"); } image = getImagePath(null, driver.driverId, "injury"); } else if (type === "turning_point_engine_regulation") { const changeType = String(params?.changeType || "minor").trim() || "minor"; const mainChangeArea = String(params?.changeArea || "").trim() || (changeType === "major" ? "hybrid system architecture" : "fuel flow monitoring"); const requestedEngineChanges = Array.isArray(params?.engineChanges) ? params.engineChanges : []; const useManualEngineChanges = !!params?.manualMode; const manualChangesByEngine = {}; const engines = queryDB(`SELECT * FROM Custom_Engines_list`, [], "allRows"); const engineStats = queryDB(`SELECT * FROM Custom_Engines_stats`, [], "allRows"); const variability = changeType === "major" ? 0.28 : 0.13; const engineData = {}; const engineBias = {}; const engineImpact = {}; const winnerNames = []; const loserNames = []; const neutralNames = []; const currentStats = {}; for (const row of engineStats) { const engineId = String(row[0]); const designId = Number(row[1]); const partStat = Number(row[2]); const unitValue = Number(row[3]); if (!currentStats[engineId]) currentStats[engineId] = {}; const engineNum = Number(engineId); if (partStat === 15 && designId === engineNum + 1) currentStats[engineId][18] = unitValue; else if (partStat === 15 && designId === engineNum + 2) currentStats[engineId][19] = unitValue; else currentStats[engineId][partStat] = unitValue; } for (const change of requestedEngineChanges) { const engineId = String(Number(change?.engineId)); if (!engineId || engineId === "NaN") continue; const requestedValue = Number(change?.value); manualChangesByEngine[engineId] = Number.isFinite(requestedValue) ? requestedValue : 0; } for (const [engineId, stats] of Object.entries(currentStats)) { const hasManualChange = useManualEngineChanges && Object.prototype.hasOwnProperty.call(manualChangesByEngine, engineId); const requestedPct = hasManualChange ? Number(manualChangesByEngine[engineId]) : null; const biasRoll = Math.random(); const bias = hasManualChange ? (requestedPct > 0 ? 1 : requestedPct < 0 ? -1 : 0) : (changeType === "major" ? (biasRoll < 0.45 ? 1 : biasRoll < 0.90 ? -1 : 0) : (biasRoll < 0.35 ? 1 : biasRoll < 0.70 ? -1 : 0)); engineBias[engineId] = bias; let sumPct = 0; let count = 0; engineData[engineId] = {}; for (const [statId, currentValue] of Object.entries(stats)) { if (Number(statId) === 11 || Number(statId) === 12) { engineData[engineId][statId] = Number(currentValue); continue; } const delta = hasManualChange ? (1 + (clampValue(requestedPct + ((Math.random() * 2.4) - 1.2), -40, 40) / 100)) : bias === 1 ? (1 + (Math.random() * variability)) : bias === -1 ? (1 - (Math.random() * variability)) : (1 + ((Math.random() * 2 * variability) - variability)); const next = Math.max(0, Math.min(100, Math.round(Number(currentValue) * delta))); engineData[engineId][statId] = next; if (Number(currentValue) > 0) { sumPct += (next - Number(currentValue)) / Number(currentValue); count += 1; } } engineImpact[engineId] = count ? (sumPct / count) : 0; const engineName = engines.find(row => String(row[0]) === String(engineId))?.[1] || `Engine ${engineId}`; if (bias === 1) winnerNames.push(engineName); else if (bias === -1) loserNames.push(engineName); else neutralNames.push(engineName); } data = { changeType, mainChangeArea, variability, manualMode: useManualEngineChanges, engineData, engineBias, engineImpact, requestedEngineChanges: useManualEngineChanges ? manualChangesByEngine : {}, winners: winnerNames, losers: loserNames, neutrals: neutralNames, winnerNames, loserNames, neutralNames, season: seasonYear }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 107, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 107, "original"); } image = getImagePath(null, "engine", "engine"); } else if (type === "turning_point_young_drivers") { const ids = Array.isArray(params?.prospectDriverIds) ? params.prospectDriverIds.map(id => Number(id)).filter(id => id > 0) : []; if (ids.length < 2) throw new Error("Pick at least two prospects"); ids.forEach((driverId, index) => assertOfficialF1DriverForCustomNews(driverId, `Prospect ${index + 1}`)); const prospects = ids.map(driverId => { const driver = getDriverAndTeamForCustomNews(driverId); return { driverId: driver.driverId, name: driver.name, age: null, position: null, points: null, teamId: driver.teamId || null, team: driver.teamName || "", series: driver.teamId ? "Formula 1" : "Junior formulas", overall: Number(getDriverOverall(driver.driverId) || 0) }; }); data = { season: seasonYear, driver1: prospects[0]?.name || "", driver2: prospects[1]?.name || "", driver3: prospects[2]?.name || "", f2Prospects: [], f3Prospects: [], freeAgentProspects: [], prospects }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 108, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 108, "original"); } image = getImagePath(null, null, "young"); } else if (type === "turning_point_aduo") { const quarter = Math.max(1, Math.min(3, Number(params?.quarter) || 1)); const quarterString = quarter === 1 ? "1st" : quarter === 2 ? "2nd" : "3rd"; const useManualEstimate = !!params?.manualMode; const requestedEngineChanges = Array.isArray(params?.engineChanges) ? params.engineChanges : []; const requestedEstimateByEngine = {}; const selectedEngineIds = new Set(); const enginesData = fetchEngines()?.[0] || []; const getPower = (engineRow) => { const stats = engineRow?.[1] || {}; const raw = stats[10] !== undefined ? stats[10] : stats["10"]; return raw == null ? null : Number(raw); }; for (const change of requestedEngineChanges) { const engineId = String(Number(change?.engineId)); if (!engineId || engineId === "NaN") continue; if (change?.enabled) { selectedEngineIds.add(engineId); } const requestedValue = Number(change?.value); requestedEstimateByEngine[engineId] = Number.isFinite(requestedValue) ? requestedValue : 0; } if (!selectedEngineIds.size) { throw new Error("Select at least one engine for this ADUO period"); } let bestEngine = null; let bestPower = -Infinity; for (const engineRow of enginesData) { const power = getPower(engineRow); if (power == null) continue; if (power > bestPower) { bestPower = power; bestEngine = engineRow; } } const threshold = bestPower * 0.92; const engineImprovements = enginesData .filter(engineRow => { const engineId = String(engineRow?.[0]); if (!selectedEngineIds.has(engineId)) return false; const power = getPower(engineRow); return power != null; }) .map(engineRow => { const stats = engineRow?.[1] || {}; const improvements = {}; for (const statId of Object.keys(stats)) { if (useManualEstimate) { const engineId = String(engineRow[0]); const requestedEstimate = Object.prototype.hasOwnProperty.call(requestedEstimateByEngine, engineId) ? Number(requestedEstimateByEngine[engineId]) : 0; improvements[statId] = Math.round(clampValue(requestedEstimate + ((Math.random() * 2) - 1), -20, 20) * 100) / 100; } else { improvements[statId] = Math.round((1 + (Math.random() * 6)) * 100) / 100; } } return { engineId: engineRow[0], name: engineRow[2], improvements }; }); if (!engineImprovements.length) throw new Error("No underperforming manufacturers were found for ADUO"); data = { season: seasonYear, quarter, leader: { engineId: bestEngine?.[0], name: bestEngine?.[2], stat10: bestPower }, thresholdStat10: threshold, engineImprovements, estimateMode: useManualEstimate ? "manual" : "random", requestedEngineChanges: useManualEstimate ? requestedEstimateByEngine : {}, quarterString, manufacturers: engineImprovements.map(entry => entry.name).join(", ").replace(/, ([^,]*)$/, " and $1") }; if (!finalTitle) { finalTitle = renderTurningPointTitleTemplate(data, 109, "original", selectedTemplateIndex) || generateTurningPointTitle(data, 109, "original"); } image = getImagePath(null, "engine", "engine"); } else if (type === "custom_new") { const prompt = String(params?.prompt || "").trim(); const customImage = String(params?.image || "").trim(); if (!prompt) throw new Error("Missing prompt"); if (!customImage) throw new Error("Missing image"); if (!finalTitle) throw new Error("Custom articles need a title"); data = { prompt, image: customImage, season_year: seasonYear }; image = `./assets/images/news/${customImage}`; overlay = null; } else { throw new Error(`Unsupported custom news type: ${type}`); } //for testing purposes assertCustomTurningPointPayload(type, data); return { id, stableKey, type, title: finalTitle || "Untitled", date, image, overlay, data, text: null, turning_point_type: type.startsWith("turning_point_") ? "original" : undefined }; } function randomRemovalOfNames(data) { let paramsWithName = ["winnerName", "pole_driver", "driver1", "driver2", "driver3", "driver_name"]; const nestedPaths = [ ["driver_affected", "name"], ["reserve_driver", "name"], ["driver_out", "name"], ["driver_in", "name"], ["driver_substitute", "name"], ["reserve", "name"], ]; // Campos planos paramsWithName.forEach(param => { const v = data[param]; if (typeof v === "string" && v.trim()) { let out = v.trim(); if (Math.random() < 0.5) { out = out.split(/\s+/).pop(); } out = news_insert_space(out); data[param] = out; } }); // Campos anidados .name nestedPaths.forEach(path => { let obj = data; for (let i = 0; i < path.length - 1; i++) { obj = obj?.[path[i]]; if (!obj) return; } const lastKey = path[path.length - 1]; const v = obj[lastKey]; if (typeof v === "string" && v.trim()) { let out = v.trim(); if (Math.random() < 0.5) { out = out.split(/\s+/).pop(); } out = (typeof news_insert_space === "function" ? news_insert_space(out) : _newsSpace(out)); obj[lastKey] = out; } }); return data; } function generateTurningPointTitle(data, new_type, turningPointType) { let dataRandomized = randomRemovalOfNames(data); let templateObj = null; templateObj = turningPointsTitleTemplates.find(t => t.new_type === new_type); const paramMap = getParamMap(dataRandomized); let titles = []; if (turningPointType === "original") { titles = templateObj.turning_titles; } else if (turningPointType === "positive") { titles = templateObj.positive_titles; } else if (turningPointType === "negative") { titles = templateObj.negative_titles; } const idx = Math.floor(Math.random() * titles.length); const tpl = titles[idx]; return tpl.replace(/{{\s*(\w+)\s*}}/g, (_, key) => paramMap[new_type][key] || ''); } function generateTitle(data, new_type) { let dataRandomized = randomRemovalOfNames(data); let templateObj = null; templateObj = newsTitleTemplates.find(t => t.new_type === new_type); let raceInfo = null; if (data.raceId) { raceInfo = getCircuitInfo(data.raceId); data.circuit = raceInfo.circuit; data.country = raceInfo.country; data.adjective = raceInfo.adjective; } const paramMap = getParamMap(dataRandomized); const titles = templateObj.titles; const idx = Math.floor(Math.random() * titles.length); const tpl = titles[idx]; return tpl.replace(/{{\s*(\w+)\s*}}/g, (_, key) => paramMap[new_type][key] || ''); } function generateF2AndF3ReviewNews(currentMonth, savedNews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], "singleRow"); const season = daySeason[1]; const newsList = []; const allRacesDone = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], "singleValue" ); const totalRaces = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], "singleValue" ); // Solo último mes + temporada terminada if (currentMonth < 11 || allRacesDone < totalRaces) { return newsList; } const F2Champion = queryDB( `SELECT bas.FirstName, bas.LastName, rds.DriverID, 1 FROM Races_DriverStandings rds JOIN Staff_BasicData bas ON rds.DriverID = bas.StaffID WHERE rds.SeasonID = ? AND rds.RaceFormula = 2 AND rds.Position = 1 LIMIT 1`, [season], "singleRow" ); const F3Champion = queryDB( `SELECT bas.FirstName, bas.LastName, rds.DriverID, 1 FROM Races_DriverStandings rds JOIN Staff_BasicData bas ON rds.DriverID = bas.StaffID WHERE rds.SeasonID = ? AND rds.RaceFormula = 3 AND rds.Position = 1 LIMIT 1`, [season], "singleRow" ); const entry = `feeder_series_review_${season}`; if (savedNews[entry]) { newsList.push({ id: entry, ...savedNews[entry] }); return newsList; } const [f2Name, f2Id] = formatNamesSimple(F2Champion || ["Unknown", "Driver", 0]); const [f3Name, f3Id] = formatNamesSimple(F3Champion || ["Unknown", "Driver", 0]); const titleData = { f2_champion: { name: news_insert_space(f2Name), driverId: f2Id }, f3_champion: { name: news_insert_space(f3Name), driverId: f3Id }, season_year: season }; const date = new Date(season, currentMonth - 1, Math.floor(Math.random() * 28) + 1); const excelDate = dateToExcel(date); const title = generateTitle({ f2_champion: titleData.f2_champion.name, f3_champion: titleData.f3_champion.name, season_year: season }, 20); const image = getImagePath(null, null, "young"); const newEntry = { id: entry, title, image, data: titleData, date: excelDate, type: "feeder_series_review" }; newsList.push(newEntry); return newsList; } export function generateFakeTransferNews(monthsDone, savedNews, bigConfirmedTransfersNews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const seasonResults = fetchSeasonResults(season); const usedDriverIdsGlobal = new Set(); Object.entries(savedNews || {}).forEach(([id, news]) => { if (!news || !news.data || !Array.isArray(news.data.drivers)) return; // Solo nos interesa mapear fechas por mes para las fake_transfer_* if (id.startsWith("fake_transfer_")) { news.data.drivers.forEach(driver => { if (driver.driverId) { usedDriverIdsGlobal.add(driver.driverId); } }); //if it is only fake_transfer_m without slot, transform it into fake_transfer_m_1 const parts = id.split("_"); if (parts.length === 3) { const newId = `${id}_1`; savedNews[newId] = news; delete savedNews[id]; } } if (id.startsWith("big_transfer_") || id.startsWith("massive_signing_") || id.startsWith("massive_exit_")) { let id = news.data.driverId; if (id) { usedDriverIdsGlobal.add(id); } } }); //also put driverids from bigConfirmedTransfersNews into usedDriverIdsGlobal bigConfirmedTransfersNews.forEach(news => { let id = news.data.driverId; if (id) { usedDriverIdsGlobal.add(id); } }); console.log("USED DRIVERS FOR FAKE TRANSFERS:", usedDriverIdsGlobal); let newsList = []; monthsDone.forEach(m => { const drivers = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.ContractType = 0 AND con.PosInTeam <= 2 AND con.TeamID IN (1,2,3,4,5,6,7,8,9,10,32)`, [], 'allRows' ); let driversWithOverall = []; drivers.forEach(d => { const overall = getDriverOverall(d[2]); const name = formatNamesSimple(d); const teamId = d[3]; const teamName = combined_dict[teamId] || "Unknown Team"; let driver = { name: name[0], driverId: name[1], team: teamName, teamId: teamId, overall: overall }; if (overall >= 88 && !usedDriverIdsGlobal.has(driver.driverId)) { driversWithOverall.push(driver); } }); //sort by overall descending driversWithOverall.sort((a, b) => b.overall - a.overall); const pointsSchema = fetchPointsRegulations(); const racesDone = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], 'singleValue' ); const mostPointsPerRace = pointsSchema.twoBiggestPoints[0]; const maxPointsPossible = mostPointsPerRace * racesDone; const championshipLeaderPoints = queryDB( `SELECT DriverID, Points FROM Races_DriverStandings WHERE SeasonID = ? AND Position = 1 AND RaceFormula = 1`, [season], 'singleRow' ); const leaderPoints = Number(championshipLeaderPoints?.[1] || 0); const thresholdGap = maxPointsPossible / 3; // Para evitar repetir piloto en el mismo mes (opcional pero lógico) // Si ya hay noticias guardadas para este mes, marcar sus drivers como usados for (let slot = 1; slot <= 2; slot++) { const existingId = `fake_transfer_${m}_${slot}`; const saved = savedNews[existingId]; if (saved && saved.data && saved.data.drivers && saved.data.drivers[0]) { const savedDriverId = saved.data.drivers[0].driverId; if (savedDriverId) usedDriverIdsGlobal.add(savedDriverId); } } for (let slot = 1; slot <= 2; slot++) { const entryId = `fake_transfer_${m}_${slot}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); continue; } const day = Math.floor(Math.random() * 30) + 1; const date = new Date(season, m - 1, day); const excelDate = dateToExcel(date); const latestRaceDoneUpToDate = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? AND State = 2 AND Day <= ?`, [season, excelDate], 'singleValue'); const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, latestRaceDoneUpToDate); const championshipLeader = driverStandings.find(ds => ds.position === 1); const leaderPoints = Number(championshipLeader?.points || 0); if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); continue; } let randomDriver = null; let randomTeamId = null; driversWithOverall.forEach(driver => { if (usedDriverIdsGlobal.has(driver.driverId)) { return; } const driverPoints = driversResults.find(dr => dr.driverId === driver.driverId)?.points; const points = Number(driverPoints || 0); const gap = leaderPoints - points; if (gap >= thresholdGap) { if (!randomDriver || driver.overall > randomDriver.overall) { randomDriver = { name: driver.name, driverId: driver.driverId, teamId: driver.teamId }; randomTeamId = driver.teamId; usedDriverIdsGlobal.add(driver.driverId); } } }); if (!randomDriver) { const top10DriversExpiringContracts = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID JOIN Races_DriverStandings sta ON dri.StaffID = sta.DriverID WHERE con.ContractType = 0 AND con.PosInTeam <= 2 AND con.EndSeason = ? AND sta.Position <= 10 AND sta.RaceFormula = 1 AND sta.SeasonID = ? AND NOT EXISTS ( SELECT 1 FROM Staff_Contracts c3 JOIN Staff_DriverData d3 ON d3.StaffID = c3.StaffID WHERE c3.TeamID = con.TeamID AND c3.PosInTeam = con.PosInTeam AND c3.ContractType = 3 );`, [season, season], 'allRows' ); if (top10DriversExpiringContracts.length) { let picked = randomPick(top10DriversExpiringContracts); //if picked is already used try to pick another one up to 5 times let attempts = 0; while (usedDriverIdsGlobal.has(picked[2]) && attempts < 5) { picked = randomPick(top10DriversExpiringContracts); attempts++; } if (usedDriverIdsGlobal.has(picked[2])) { picked = randomPick(drivers) } const [nameFormatted, driverId] = formatNamesSimple(picked); randomDriver = { name: nameFormatted, driverId: driverId, teamId: picked[3] }; usedDriverIdsGlobal.add(randomDriver.driverId); randomTeamId = picked[3]; } } if (randomDriver) { usedDriverIdsGlobal.add(randomDriver.driverId); const nameFormatted = randomDriver.name; const driverId = randomDriver.driverId; const newData = { drivers: [{ name: news_insert_space(nameFormatted), driverId, team: combined_dict[randomTeamId], teamId: randomTeamId, previouslyDrivenTeams: getPreviouslyDrivenTeams(driverId), }], }; const titleData = { driver1: news_insert_space(nameFormatted), team1: combined_dict[randomTeamId], }; const title = generateTitle(titleData, 7); const image = getImagePath(randomTeamId, driverId, "transfer"); newsList.push({ id: entryId, type: "fake_transfer", title, date: excelDate, image: image, overlay: "fake-transfer-overlay", data: newData, text: null }); } } }); return newsList; } const randomPick = arr => arr[Math.floor(Math.random() * arr.length)]; export function generateBigConfirmedTransferNews(savedNews = {}, currentMonth) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const month = currentMonth > 9 ? 9 : currentMonth; const drivers = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.ContractType = 0 AND con.PosInTeam <= 2`, [], 'allRows' ); const driversWithHighOverall = []; drivers.forEach(d => { const overall = getDriverOverall(d[2]); const name = formatNamesSimple(d); const teamId = d[3]; const teamName = combined_dict[teamId] || "Unknown Team"; let driver = { name: name[0], driverId: name[1], team: teamName, teamId: teamId, overall: overall }; if (overall >= 88) { driversWithHighOverall.push(driver); } }); let newsList = []; //iterate through each list driversWithHighOverall.forEach(driver => { const contract = queryDB(`SELECT TeamID, Salary, EndSeason FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 3 AND TeamID != ?`, [driver.driverId, driver.teamId], 'singleRow'); if (!contract) return; const futureTeamId = contract[0] let titleData = { driver1: driver.name, driverId: driver.driverId, team1: driver.team, team2Id: futureTeamId, salary: contract[1], endSeason: contract[2], previouslyDrivenTeams: getPreviouslyDrivenTeams(driver.driverId), team2: combined_dict[futureTeamId], season_year: season } if (driver.overall < 90) { const entryId = `new_big_transfer_${driver.driverId}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); return; } const title = generateTitle(titleData, 6); const image = getImagePath(futureTeamId, driver.driverId, "transfer"); const day = Math.floor(Math.random() * 30) + 1; const date = new Date(season, month - 1, day); const excelDate = dateToExcel(date); titleData.date = excelDate; newsList.push({ id: entryId, type: "big_transfer", title, date: excelDate, image, overlay: "massive-signing-overlay", data: titleData, text: null }); } else { const entryId1 = `massive_exit_${driver.driverId}`; if (savedNews[entryId1]) { newsList.push({ id: entryId1, ...savedNews[entryId1] }); return; } const title1 = generateTitle(titleData, 17); const image1 = getImagePath(driver.teamId, driver.driverId, "transfer"); const day1 = Math.floor(Math.random() * 30) + 1; const date1 = new Date(season, month - 1, day1); const excelDate1 = dateToExcel(date1); titleData.date = excelDate1; titleData.team2Id = driver.teamId; newsList.push({ id: entryId1, type: "massive_exit", title: title1, date: excelDate1, image: image1, overlay: "massive-exit-overlay", data: titleData, text: null }); const entryId2 = `massive_signing_${driver.driverId}`; if (savedNews[entryId2]) { newsList.push({ id: entryId2, ...savedNews[entryId2] }); return; } const title2 = generateTitle(titleData, 18); const image2 = getImagePath(futureTeamId, driver.driverId, "transfer"); const excelDate2 = excelDate1 + 1; titleData.date = excelDate2; titleData.team2Id = futureTeamId; newsList.push({ id: entryId2, type: "massive_signing", title: title2, date: excelDate2, image: image2, overlay: "massive-signing-overlay", data: titleData, text: null }); } }); return newsList; } function buildGridLineupsData(season) { const globals = getGlobals(); const teamIds = globals.isCreateATeam ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const currentTeamFilterSql = globals.isCreateATeam ? `(TeamID BETWEEN 1 AND 10 OR TeamID = 32)` : `(TeamID BETWEEN 1 AND 10)`; const currentContractsRows = queryDB( `SELECT StaffID, TeamID FROM Staff_Contracts WHERE ContractType = 0 AND PosInTeam <= 2 AND EndSeason >= ? AND ${currentTeamFilterSql}`, [season], 'allRows' ) || []; const currentTeamByDriver = new Map(); currentContractsRows.forEach((row) => { const driverId = Number(row[0]); const teamId = Number(row[1]); if (!Number.isNaN(driverId) && !Number.isNaN(teamId)) { currentTeamByDriver.set(driverId, teamId); } }); const teamsDict = {}; teamIds.forEach(teamId => { const teamName = combined_dict[teamId] || "Unknown Team"; const teamInfo = { name: teamName, teamId, driversNextSeason: [], driversThisSeason: [] }; const driversThisSeason = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID, con.ContractType, con.PosInTeam, num.Number FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID LEFT JOIN Staff_DriverNumbers num ON num.CurrentHolder = bas.StaffID WHERE con.TeamID = ? AND con.PosInTeam <= 2 AND con.ContractType = 0 AND con.EndSeason >= ? ORDER BY con.PosInTeam, con.ContractType, dri.StaffID`, [teamId, season], 'allRows' ) || []; const driversNextSeason = queryDB( `SELECT bas.FirstName, bas.LastName, dri.StaffID, con.TeamID, con.ContractType, con.PosInTeam, num.Number FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID LEFT JOIN Staff_DriverNumbers num ON num.CurrentHolder = bas.StaffID WHERE con.TeamID = ? AND con.PosInTeam <= 2 AND con.ContractType IN (0,3) AND con.EndSeason > ? ORDER BY con.PosInTeam, con.ContractType, dri.StaffID`, [teamId, season], 'allRows' ) || []; const seenThisSeason = new Set(); driversThisSeason.forEach(d => { const name = formatNamesSimple(d); const driverId = Number(name[1]); if (seenThisSeason.has(driverId)) return; seenThisSeason.add(driverId); const driverNumber = Number(d[6]); teamInfo.driversThisSeason.push({ name: news_insert_space(name[0]), driverId, posInTeam: Number(d[5]) || null, contractType: Number(d[4]) || 0, driverNumber: !Number.isNaN(driverNumber) && driverNumber > 0 ? driverNumber : null }); }); const seenNextSeason = new Set(); driversNextSeason.forEach(d => { const name = formatNamesSimple(d); const contractType = Number(d[4]) || 0; const driverId = Number(name[1]); if (seenNextSeason.has(driverId)) return; seenNextSeason.add(driverId); const driverNumber = Number(d[6]); const currentTeamId = Number(currentTeamByDriver.get(driverId)); const isTeamChangeForNextSeason = contractType === 3 && currentTeamId !== teamId; teamInfo.driversNextSeason.push({ name: news_insert_space(name[0]), driverId, posInTeam: Number(d[5]) || null, contractType, driverNumber: !Number.isNaN(driverNumber) && driverNumber > 0 ? driverNumber : null, isForNextSeason: isTeamChangeForNextSeason }); }); teamsDict[teamId] = teamInfo; }); return { teamIds, teamsDict }; } export function getCurrentAndNextSeasonGridLineups() { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const { teamIds, teamsDict } = buildGridLineupsData(season); return { season, teamIds, teams: teamsDict }; } function generateNextSeasonGridNews(savedNews = {}, currentMonth) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const newsList = []; const allRacesDone = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], 'singleValue' ); const totalRaces = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], 'singleValue' ); //if we are not in the last month or not all races done, return empty list if (currentMonth < 11 || allRacesDone < totalRaces) { return newsList; } const entryId = `next_season_grid`; if (savedNews[entryId]) { return [{ id: entryId, ...savedNews[entryId] }]; } const { teamsDict } = buildGridLineupsData(season); const title = generateTitle({ season_year: season + 1 }, 19); const image = getImagePath(null, null, "grid"); const newsDate = new Date(season, 11, 15); const excelDate = dateToExcel(newsDate); newsList.push({ id: entryId, type: "next_season_grid", title, date: excelDate, image, overlay: "next-season-grid", data: { season_year: season + 1, teams: teamsDict }, text: null }); return newsList; } export function generateContractRenewalsNews(savedNews = {}, contractRenewals = [], currentMonth) { const renewalMonths = [8, 9, 10].filter(m => m < currentMonth); const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const used = new Set( Object.values(savedNews) .filter(n => n?.type === "contract_renewal" && n?.data?.driverId != null) .map(n => n.data.driverId) ); const newsList = []; //iterate through months done of renewalMonths for (const m of renewalMonths) { const entryId = `contract_renewal_${m}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); const dId = savedNews[entryId]?.data?.driverId; if (dId != null) used.add(dId); continue; } const pool = contractRenewals.filter(c => !used.has(c.driverId)); if (!pool.length) continue; const contract = randomPick(pool); used.add(contract.driverId); const title = generateTitle(contract, 10); const image = getImagePath(contract.team1Id, contract.driverId, "transfer"); // Generate a date from the current month const newsDate = new Date(season, m - 1, Math.floor(Math.random() * 28) + 1); const excelDate = dateToExcel(newsDate); newsList.push({ id: entryId, type: "contract_renewal", title, date: excelDate, image, overlay: "contract-renewal-overlay", data: contract, text: null }); } return newsList; } export function getContractExtensions() { const daySeason = queryDB( `SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow' ) const seasonYear = daySeason[1] let contractRenewals = queryDB( ` SELECT bas.FirstName, bas.LastName, con3.StaffID, con3.TeamID, con3.EndSeason, con3.Salary FROM Staff_Contracts con3 JOIN Staff_DriverData dri ON con3.StaffID = dri.StaffID JOIN Staff_BasicData bas ON con3.StaffID = bas.StaffID WHERE con3.ContractType = 3 AND con3.PosInTeam <= 2 AND EXISTS ( SELECT 1 FROM Staff_Contracts con0 WHERE con0.StaffID = con3.StaffID AND con0.TeamID = con3.TeamID AND con0.ContractType = 0 AND con0.PosInTeam <= 2 ); ` , [], 'allRows') // contractRenewals.forEach(contract => { // let driverID = contract[2]; // const driverOverall = getDriverOverall(driverID); // if (driverOverall < 88) { // contractRenewals = contractRenewals.filter(c => c[2] !== driverID); // } // }); const formattedContracts = contractRenewals.map(contract => { const [nameFormatted, driverId, teamId] = formatNamesSimple(contract); const currentTeam = queryDB(`SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0`, [driverId], 'singleValue'); return { driver1: nameFormatted, driverId: driverId, team1: combined_dict[teamId], team2: combined_dict[currentTeam], team1Id: teamId, team2Id: currentTeam, previouslyDrivenTeams: getPreviouslyDrivenTeams(driverId), endSeason: contract[4], salary: contract[5] }; }); return formattedContracts; } export function getTrueTransferRumors() { const daySeason = queryDB( `SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow' ) const seasonYear = daySeason[1] const top5rows = queryDB( `SELECT DriverID FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ? ORDER BY Position LIMIT 5`, [seasonYear], 'allRows' ) const top5DriverIDs = top5rows.map(r => r[0]) const sql = ` SELECT bas.FirstName, bas.LastName, dri.StaffID, ofe.TeamID as PotentialTeam, ofe.ContractState, ofe.PosInTeam, enu.Name as State, ofe.OfferDay, ofe.Salary, ofe.SalaryOpinion, ofe.EndSeason, ofe.LengthOpinion, ofe.ExpirationDay, con.TeamID FROM Staff_BasicData bas RIGHT JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_ContractOffers ofe ON bas.StaffID = ofe.StaffID JOIN Staff_Enum_ContractState enu ON ofe.ContractState = enu.Value JOIN Staff_Contracts con ON bas.StaffID = con.StaffID WHERE con.ContractType = 0 AND ofe.PosInTeam <= 2 ` const rows = queryDB(sql, [], 'allRows') .filter(r => { const date = excelToDate(r[7]) return date.getFullYear() === seasonYear }) const formatted = rows.map(r => { const [nameFormatted, driverId] = formatNamesSimple(r) let actualTeam = queryDB(`SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0`, [r[2]], 'singleValue') actualTeam = combined_dict[actualTeam] let driverAtRisk = queryDB(`SELECT bas.FirstName, bas.LastName, con.StaffID, con.TeamID FROM Staff_Contracts con JOIN Staff_BasicData bas ON bas.StaffID = con.StaffID RIGHT JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID WHERE con.TeamID = ? AND con.PosInTeam = ? AND con.ContractType = 0`, [r[3], r[5]], 'singleRow') //if driverAtRisk[2] is th same as r[2] then remove it if (driverAtRisk[2] === r[2]) return null driverAtRisk = formatNamesSimple(driverAtRisk) const driverAtRiskName = news_insert_space(driverAtRisk[0]) return { name: news_insert_space(nameFormatted), driverId, actualTeam, actualTeamId: r[13], potentialTeam: combined_dict[r[3]], state: r[6], offerDay: excelToDate(r[7]), expirationDate: excelToDate(r[12]), posInTeam: r[5], driverAtRisk: driverAtRiskName, salary: r[8], salaryOpinion: opinionDict[r[9]], endSeason: r[10], lengthOpinion: opinionDict[r[11]], overall: getDriverOverall(driverId), } }).filter(r => r !== null); const seen = new Set() const uniqueFormatted = formatted.filter(item => { const key = JSON.stringify(item) if (seen.has(key)) { return false } else { seen.add(key) return true } }) const tier1OrTop5 = [] const others = [] for (const rumor of uniqueFormatted) { const [tier] = getTier(rumor.driverId) const isTop5 = top5DriverIDs.includes(rumor.driverId) if (tier === 1 || isTop5) { tier1OrTop5.push(rumor) } else { others.push(rumor) } } return { tier1OrTop5, others } } export function getConfirmedTransfers(bestDrivers = false) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const day = daySeason[0]; const seasonYear = daySeason[1]; let futureContracts = queryDB( `SELECT DISTINCT bas.FirstName, bas.LastName, con3.StaffID, con3.TeamID, con3.EndSeason, con3.Salary FROM Staff_Contracts con3 JOIN Staff_DriverData dri ON con3.StaffID = dri.StaffID JOIN Staff_BasicData bas ON con3.StaffID = bas.StaffID WHERE con3.ContractType = 3 AND con3.PosInTeam <= 2 AND EXISTS ( SELECT 1 FROM Staff_Contracts con0 WHERE con0.StaffID = con3.StaffID AND con0.ContractType = 0 AND con0.PosInTeam <= 2 AND con0.TeamID <> con3.TeamID -- equipos distintos -- Opcional: filtros por temporada si aplica, por ejemplo: -- AND con0.EndSeason = con3.EndSeason );` , [], 'allRows') if (bestDrivers) { futureContracts.forEach(contract => { let driverID = contract[2]; const driverOverall = getDriverOverall(driverID); if (driverOverall < 88) { futureContracts = futureContracts.filter(c => c[2] !== driverID); } }); } const formattedContracts = futureContracts.map(contract => { const [nameFormatted, driverId, teamId] = formatNamesSimple(contract); const currentTeam = queryDB(`SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0`, [driverId], 'singleValue'); return { driver1: nameFormatted, driverId: driverId, team1: combined_dict[teamId], team2: combined_dict[currentTeam], team1Id: teamId, team2Id: currentTeam, previouslyDrivenTeams: getPreviouslyDrivenTeams(driverId), endSeason: contract[4], salary: contract[5] }; }); return formattedContracts; } export function generateTransferRumorsNews(offers, savedNews) { if (!offers) return null; const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const day = daySeason[0]; const seasonYear = daySeason[1]; let newsList = []; const realDay = excelToDate(day); if (realDay.getMonth() + 1 < 8 || (realDay.getMonth() + 1 === 8 && realDay.getDate() < 10)) { return null; } const date = dateToExcel(new Date(seasonYear, 7, 10)); const validOffers = offers.others.filter(item => item.state !== 'Signed'); const driversDict = validOffers.reduce((acc, item) => { const previousResultsTeam = queryDB(`SELECT SeasonID, Points, Position FROM Races_TeamStandings WHERE TeamID = ?`, [item.actualTeamId], 'allRows') .map(r => { return { season: r[0], points: r[1], position: r[2] } }); const { driverId, name, overall, actualTeam, teamId, previouslyDrivenTeams, ...offerDetails } = item; if (!acc[driverId]) { acc[driverId] = { driverId, name, overall, actualTeam, teamId: item.actualTeamId, previouslyDrivenTeams: getPreviouslyDrivenTeams(driverId), previousResultsTeam: previousResultsTeam, offers: [] }; } acc[driverId].offers.push(offerDetails); return acc; }, {}); const top3Drivers = Object .values(driversDict) .filter(driver => driver.offers.length > 0) .sort((a, b) => b.overall - a.overall) .slice(0, 3); // Si no hay suficientes pilotos, no generamos la noticia if (top3Drivers.length < 3) { return null; } let titleData = { driver1: top3Drivers[0].name, driver2: top3Drivers[1].name, driver3: top3Drivers[2].name, team1: top3Drivers[0]["offers"][0].potentialTeam, team2: top3Drivers[1]["offers"][0].potentialTeam, team3: top3Drivers[2]["offers"][0].potentialTeam, } const title = generateTitle(titleData, 4); const sillySeasonRumorsId = `silly_season_${seasonYear}`; if (savedNews[sillySeasonRumorsId]) { newsList.push({ id: sillySeasonRumorsId, ...savedNews[sillySeasonRumorsId] }); return newsList; } const driversArray = Object.values(driversDict); const image = getImagePath(top3Drivers[0].teamId, top3Drivers[0].driverId, "transfer_generic"); const sillySeasonNew = { id: sillySeasonRumorsId, type: "silly_season_rumors", title: title, date: date, season: seasonYear, image: image, overlay: "silly-season-overlay", data: { drivers: driversArray }, text: null }; newsList.push(sillySeasonNew); return newsList } export function generateTeamsUpgradesNews(events, savednews) { //aparcado de momento const globals = getGlobals(); let teamIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] if (globals.isCreateATeam) { teamIds.push(32); } const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonYear = daySeason[1]; const parts = getAllPartsFromTeam(32); events.forEach(raceId => { const entryId = `${seasonYear}_upgrades_${raceId}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return; } let nextRaceId = raceId + 1; let trackIdNextRace = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [nextRaceId], 'singleValue'); let newParts = {}; teamIds.forEach(teamId => { let teamName = combined_dict[teamId]; newParts[teamId] = []; let parts = getAllPartsFromTeam(teamId); //iterate through the parts dictionary for (const part in parts) { let partsArray = parts[part] partsArray.forEach(partDetails => { let trackDebutForPart = partDetails[3]; if (trackDebutForPart === trackIdNextRace) { newParts[teamId].push(part); return; } }); } }); }); } export function generateComparisonNews(comparisonMonths, savedNews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const season = daySeason[1]; const currentDate = excelToDate(daySeason[0]); const currentMonth = currentDate.getMonth() + 1; let newsList = []; comparisonMonths.forEach(month => { if (month >= currentMonth) return; //only past months const randomDay1 = Math.floor(Math.random() * 31) + 1; const date = new Date(season, month - 1, randomDay1); const excelDate = dateToExcel(date); if (month % 2 !== 0) { let shifts = calculateTeamDropsByDate(season, date); //order by shift.drop shifts.sort((a, b) => a.drop - b.drop); const top3 = shifts.slice(0, 3); const bottom3 = shifts.slice(-3); //put together in the same array const combined = [...top3, ...bottom3]; const teamToTalk = randomPick(combined); //create the new const entryId = `team_comparison_${season}_${month}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); return; } let newTypeId, compType; if (teamToTalk.drop > 0) { newTypeId = 11; compType = "bad" } else { newTypeId = 12; compType = "good"; } const title = generateTitle({ teamId: combined_dict[teamToTalk.teamId], season }, newTypeId); const image = getImagePath(teamToTalk.teamId, teamToTalk.teamId, "teamComparison"); const newsEntry = { id: entryId, type: "team_comparison", title: title, date: excelDate, image: image, overlay: null, data: { team: teamToTalk, season: season, compType: compType }, text: null }; newsList.push(newsEntry); } else { const isCreateATeam = getGlobals().isCreateATeam; const teams = isCreateATeam ? [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; const teamId = randomPick(teams); const teamName = combined_dict[teamId]; const entryId = `driver_comparison_${season}_${month}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); return; } const drivers = queryDB(`SELECT bas.FirstName, bas.LastName, dri.StaffID FROM Staff_BasicData bas JOIN Staff_DriverData dri ON bas.StaffID = dri.StaffID JOIN Staff_Contracts con ON bas.StaffID = con.StaffID JOIN Races_DriverStandings sta ON dri.StaffID = sta.DriverID WHERE sta.SeasonID = ? AND sta.RaceFormula = 1 AND con.TeamID = ? AND con.ContractType = 0 AND con.PosInTeam <= 2 ORDER BY sta.Position`, [season, teamId], 'allRows'); if (drivers.length < 2) return; const formattedDrivers = drivers.map(driver => { const [nameFormatted, driverId] = formatNamesSimple(driver); return { name: news_insert_space(nameFormatted), driverId } }); //for each driver, select randomlky if taking his entire name or only last name formattedDrivers.forEach(driver => { if (Math.random() < 0.5) { const parts = driver.name.split(' '); driver.name = parts[parts.length - 1]; //last name only } }); let data = { team: teamName, driver1: formattedDrivers[0].name, driver2: formattedDrivers[1].name, } const title = generateTitle(data, 13); // const image = getImagePath(teamId, teamId, "driverComparison"); const overlay = "driver-comparison-overlay"; const newsEntry = { id: entryId, type: "driver_comparison", title: title, date: excelDate, image: null, overlay: overlay, data: { teamId, teamName, drivers: formattedDrivers, season }, text: null }; newsList.push(newsEntry); } }); return newsList; } export function generateRaceResultsNews(events, savednews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonYear = daySeason[1]; let newsList = []; events.forEach(raceId => { const entryId = `${seasonYear}_race_${raceId}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return; } const results = getOneRaceResults(raceId); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4] }; }); const podium = formatted.filter(r => r.pos <= 3); const winnerEntry = podium.find(r => r.pos === 1); const winnerName = winnerEntry ? winnerEntry.name : "Unknown"; let data = { raceId, seasonYear, winnerName: winnerName, } const title = generateTitle(data, 2); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)]; const image = getImagePath(formatted[0].teamId, code, "raceQuali"); const overlay = "race-overlay" const newsData = { first: formatted[0].name, second: formatted[1].name, third: formatted[2].name, firstTeam: formatted[0].teamId, secondTeam: formatted[1].teamId, thirdTeam: formatted[2].teamId, trackId: trackId, seasonYear: seasonYear, } const date = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); const newsEntry = { id: entryId, type: "race_result", title: title, date: date, image: image, overlay: overlay, data: newsData, text: null }; newsList.push(newsEntry); }); return newsList; } export function generateRaceReactionsNews(events, savednews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonYear = daySeason[1]; let newsList = []; events.forEach(raceId => { const entryId = `${seasonYear}_race_reaction_${raceId}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return; } // //30% chance of not generating // if (Math.random() < 0.3) { // return; // } const results = getOneRaceResults(raceId); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, teamId, teamName: combined_dict[teamId], pos: row[4], rating: getDriverOverall(driverId) }; }); const unhappyDrivers = formatted.filter(r => r.rating >= 88 && r.pos > 6); if (unhappyDrivers.length === 0) { //get the lowest 88 or more overall driver const sortedByOverall = formatted.filter(r => r.rating >= 88).sort((a, b) => b.rating - a.rating); if (sortedByOverall.length > 0) { unhappyDrivers.push(sortedByOverall[sortedByOverall.length - 1]); } } if (unhappyDrivers.length === 0) { //ge tthe driver with 86 or more rating in the lowest position const sortedByOverall = formatted.filter(r => r.rating >= 86).sort((a, b) => b.rating - a.rating); if (sortedByOverall.length > 0) { unhappyDrivers.push(sortedByOverall[sortedByOverall.length - 1]); } } //if sitll no unhappyDrivers, get the last one if (unhappyDrivers.length === 0) { unhappyDrivers.push(formatted[formatted.length - 1]); } const randomUnHappyDriver = randomPick(unhappyDrivers); //get all drivers who finished in the top 4 const happyDrivers = formatted.filter(r => r.pos <= 4); const randomHappyDriver = randomPick(happyDrivers); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)].toLowerCase(); let titleData = { raceId, allHappyDrivers: happyDrivers, allUnhappyDrivers: unhappyDrivers, randomHappyDriver, happyTeam: randomHappyDriver.teamName, unhappyTeam: randomUnHappyDriver.teamName, randomUnHappyDriver, seasonYear, trackId: trackId } const title = generateTitle(titleData, 16); let driverTeamIdInTitle = null; //check if in the title there is randomHappyDriver name or randomUnHappyDriver name if (title.includes(randomUnHappyDriver.name)) { driverTeamIdInTitle = randomUnHappyDriver.teamId; titleData.driverTeamIdInTitle = driverTeamIdInTitle; } else if (title.includes(randomHappyDriver.name)) { driverTeamIdInTitle = randomHappyDriver.teamId; titleData.driverTeamIdInTitle = driverTeamIdInTitle; } const image = getImagePath(driverTeamIdInTitle, code, "reaction"); const date = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue'); const newsEntry = { id: entryId, type: "race_reaction", title: title, date: date + 1, image: image, overlay: "reaction-overlay", data: titleData, text: null }; newsList.push(newsEntry); }); return newsList; } export function generateQualifyingResultsNews(events, savednews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonYear = daySeason[1]; let newsList = []; events.forEach(raceId => { const entryId = `${seasonYear}_quali_${raceId}`; if (savednews[entryId]) { newsList.push({ id: entryId, ...savednews[entryId] }); return; } const results = getOneQualifyingResults(raceId); const formatted = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4], fastestLap: row[5] }; }); const podium = formatted.filter(r => r.pos <= 3); const winnerEntry = podium.find(r => r.pos === 1); const poleDriver = winnerEntry ? winnerEntry.name : "Unknown"; let data = { raceId, seasonYear, pole_driver: poleDriver, } const title = generateTitle(data, 1); const trackId = queryDB(`SELECT TrackID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const code = races_names[parseInt(trackId)]; const image = getImagePath(formatted[0].teamId, `${code}_car`, "raceQuali"); const overlay = "quali-overlay" const newsData = { first: formatted[0].name, second: formatted[1].name, third: formatted[2].name, firstTeam: formatted[0].teamId, secondTeam: formatted[1].teamId, thirdTeam: formatted[2].teamId, trackId: trackId, seasonYear: seasonYear, } const date = queryDB(`SELECT Day FROM Races WHERE RaceID = ?`, [raceId], 'singleValue') - 1; const newsEntry = { id: entryId, type: "quali_result", title: title, date: date, image: image, overlay: overlay, data: newsData, text: null }; newsList.push(newsEntry); }); return newsList; } export function generateSeasonReviewNews(savedNews) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const nRaces = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [daySeason[1]], 'singleValue'); let racesInterval = nRaces / 3; const racesCompleted = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [daySeason[1]], 'singleValue'); let newsList = []; const firstRaceSeasonId = queryDB(`SELECT MIN(RaceID) FROM Races WHERE SeasonID = ?`, [daySeason[1]], 'singleValue'); const seasonResults = fetchSeasonResults(daySeason[1]); let reviewPoints = []; if (racesCompleted >= racesInterval) { reviewPoints.push({ part: 1, totalParts: 3 }); } if (racesCompleted >= racesInterval * 2) { reviewPoints.push({ part: 2, totalParts: 3 }); } if (racesCompleted >= nRaces) { reviewPoints.push({ part: 3, totalParts: 3 }); } reviewPoints.forEach(review => { const entryId = `season_review_${daySeason[1]}_${review.part}`; if (savedNews[entryId]) { newsList.push({ id: entryId, ...savedNews[entryId] }); return; } const raceIdInPoint = firstRaceSeasonId + Math.floor(racesInterval * review.part) - 1; const nextRace = raceIdInPoint + 1; const dates = queryDB(`SELECT Day FROM Races WHERE RaceID IN (?, ?)`, [raceIdInPoint, nextRace], 'allRows'); //pick a random date between those two dates let date; if (dates.length === 2) { const day1 = dates[0][0] + 1; const day2 = dates[1][0] - 1; date = Math.floor(Math.random() * (day2 - day1 + 1)) + day1; } if (review.part === 3 || !date) { date = dateToExcel(new Date(daySeason[1], 11, 10)); } const excelDate = date const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, raceIdInPoint); const firstDriver = driverStandings[0]; const firstTeam = teamStandings[0]; const secondDriver = driverStandings[1]; const secondTeam = teamStandings[1]; const thirdDriver = driverStandings[2]; const thirdTeam = teamStandings[2]; let data = { season: daySeason[1], part: review.part, driver1: firstDriver.name, driver2: secondDriver.name, } let titleId; if (review.part === 3) titleId = 15; else titleId = 14; const title = generateTitle(data, titleId); const image = getImagePath(firstTeam ? firstTeam.id : 1, firstDriver ? firstDriver.id : 1, "season_review"); let newsData = { season: daySeason[1], part: review.part, firstDriver, secondDriver, firstTeam, secondTeam } newsList.push({ id: entryId, type: "season_review", title: title, date: excelDate, image: image, overlay: null, data: newsData, text: null }); }); return newsList; } export function news_insert_space(str) { return str .replace(/([A-Z])/g, ' $1') .trim() .replace(/\s+/g, ' '); } export function getOneQualiDetails(raceId) { const results = getOneQualifyingResults(raceId); if (!results.length) return {}; const season = queryDB(`SELECT SeasonID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const seasonResults = fetchQualiResults(season); const seasonRaceResults = fetchSeasonResults(season, true); //to get standings const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, raceId - 1, true, true, true); //get championship standings before that race const { driverStandings: raceDriverStandings, teamStandings: raceTeamStandings, driversResults: driverRaceResults, racesNames: raceRacesNames } = rebuildStandingsUntil(seasonRaceResults, raceId - 1, true, true, false); // 1) Obtenemos time y laps del ganador (primera fila) const poleTime = results[0][5]; // índice 5 = res.FastestLap const poleDetails = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); const time = row[5]; //format to mins:secs.millis, time is in seconds.milis const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); const millis = Math.round((time % 1) * 1000); const fastestLap = `${minutes}:${seconds.toString().padStart(2, "0")}.${millis .toString() .padStart(3, "0")}`; const gapToPole = time - poleTime; return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4], gapToPole, fastestLap: fastestLap }; }); const champions = getLatestChampions(season); const numberOfRaces = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], 'singleRow')[0]; const drivers = seasonResults.map(r => ({ id: r.driverId, name: r.driverName ?? "" })); const enrichedAllTime = enrichDriversWithHistory(drivers); return { details: poleDetails, driverStandings, teamStandings, driverQualiResults: driversResults, racesNames, champions, nRaces: numberOfRaces, enrichedAllTime, driversResults: driverRaceResults } } export function getOneRaceDetails(raceId) { const results = getOneRaceResults(raceId); if (!results.length) return {}; const sprintResults = getOneRaceResults(raceId, true); const season = queryDB(`SELECT SeasonID FROM Races WHERE RaceID = ?`, [raceId], 'singleRow'); const seasonResults = fetchSeasonResults(season, true); const pointsSchema = fetchPointsRegulations(); const maxPointsPerRace = pointsSchema.twoBiggestPoints[0] const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, raceId); const remainingRaces = queryDB(`SELECT RaceID, TrackID, WeekendType FROM Races WHERE SeasonID = ? AND RaceID > ? ORDER BY RaceID`, [season, raceId], 'allRows'); //make an object that has raceid, trackId, and race track const remainingRacesDetails = remainingRaces.map(r => { return { raceId: r[0], trackId: r[1], sprint: r[2] === 1, trackName: countries_data[races_names[r[1]]].country } }); // 1) Obtenemos time y laps del ganador (primera fila) const winnerTime = results[0][10]; // índice 10 = res.Time const winnerLaps = results[0][11]; // índice 11 = res.Laps const top3DoD = getDoDTopNForRace(season, raceId, 3); //for each of the top3, changen name for news_insert_space(name), and leave the rest the same const driverOfTheDayInfo = top3DoD.map((dod, index) => { const newName = news_insert_space(dod.name); return { name: newName, ...dod, }; }); const raceDetails = results.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); const time = row[10]; const laps = row[11]; const gapToWinner = time - winnerTime; const gapLaps = winnerLaps - laps; const base = { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4], grid: row[5], points: row[6], dnf: row[7], safetyCar: row[8], virtualSafetyCar: row[9], gapToWinner, gapLaps, overall: getDriverOverall(driverId), pointsGapToLeader: driverStandings.find(d => d.driverId === driverId)?.gapToLeader }; return base; }); let sprintDetails = []; if (sprintResults.length > 0) { const sprintWinnerTime = sprintResults[0][7]; const sprintWinnerLaps = sprintResults[0][8]; sprintDetails = sprintResults.map(row => { const [nameFormatted, driverId, teamId] = formatNamesSimple(row); const time = row[7]; const laps = row[8]; const gapToWinner = time - sprintWinnerTime; const gapLaps = sprintWinnerLaps - laps return { name: news_insert_space(nameFormatted), driverId, teamId, pos: row[4], points: row[5], dnf: row[6], gapToWinner, gapLaps, }; }); } const champions = getLatestChampions(season); const numberOfRaces = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ?`, [season], 'singleRow')[0]; const drivers = seasonResults.map(r => ({ id: r.driverId, name: r.driverName ?? "" })); const enrichedAllTime = enrichDriversWithHistory(drivers); return { details: raceDetails, driverStandings, teamStandings, driversResults, racesNames, champions, nRaces: numberOfRaces, sprintDetails, pointsSchema, remainingRaces: remainingRacesDetails, enrichedAllTime, driverOfTheDayInfo } } function getOneRaceResults(raceId, sprint = false) { const sql = ` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.FinishingPos, ${sprint ? "" : "res.StartingPos, "} ${sprint ? "res.ChampionshipPoints," : "res.Points, "} res.DNF, ${sprint ? "" : "res.SafetyCarDeployments, "} ${sprint ? "" : "res.VirtualSafetyCarDeployments, "} ${sprint ? "res.RaceTime," : "res.Time, "} ${sprint ? "res.LapCount" : "res.Laps"} FROM Staff_BasicData bas JOIN ${sprint ? "Races_SprintResults" : "Races_Results"} res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? ${sprint ? "AND res.RaceFormula = 1" : ""} ORDER BY res.FinishingPos `; const rows = queryDB(sql, [raceId], 'allRows'); return rows; } function getOneQualifyingResults(raceId) { const sql = ` SELECT bas.FirstName, bas.LastName, res.DriverID, res.TeamID, res.FinishingPos, res.FastestLap FROM Staff_BasicData bas JOIN Races_QualifyingResults res ON bas.StaffID = res.DriverID WHERE res.RaceID = ? AND res.RaceFormula = 1 AND res.QualifyingStage = (SELECT MAX(res2.QualifyingStage) FROM Races_QualifyingResults res2 WHERE res2.RaceID = ? AND res2.DriverID = res.DriverID) AND SprintShootout = 0 ORDER BY res.FinishingPos; ` const rows = queryDB(sql, [raceId, raceId], 'allRows'); return rows; } export function rebuildStandingsUntil(seasonResultsRaw, raceId, includeCurrentRacePrevResults = false, includeCurrentRacePoints = true, isQuali = false) { //includeCurrentRacePrevResults only limits previous reace results string, points are always summed up until raceId const seasonResults = (seasonResultsRaw || []) .map(d => (d?.data && typeof d.data === "object") ? d.data : d) .filter(d => d && typeof d.driverName === "string" && Array.isArray(d.races)); const driverMap = {}; const teamMap = {}; let racesNames = []; const driversResults = []; let maxPoints = 0; seasonResults.forEach(driverRec => { const name = driverRec.driverName; let resultsString = ""; let driverRaces = []; let nPodiums = 0; let nWins = 0; let nPointsFinishes = 0; const races = driverRec.races; // 2) Suma de puntos hasta raceId (incluye la actual; el detalle depende de includeCurrentRacePrevResults) const totalDriverPoints = races.reduce((sum, r) => { const thisRaceId = Number(r.raceId); if (thisRaceId <= raceId) { // if includeCurrentRacePrevResults is flase, don't add currentraceId to previous results string if (thisRaceId < raceId || includeCurrentRacePrevResults) { driverRaces.push(getCircuitInfo(thisRaceId).country); const fin = parseInt(r.finishingPos); if (!isQuali) { resultsString += (fin !== -1 ? `P${fin}` : "DNF") + ", "; } else { resultsString += (r.qualifyingPos !== 99 ? `P${r.qualifyingPos}` : `P${r.startingPos}`) + ", "; } if (fin === 1) nWins++; if (fin > 0 && fin <= 3) nPodiums++; if ((parseInt(r.points) || 0) > 0) nPointsFinishes++; } // if includeCurrentRacePoints is true, sum points from current race if (thisRaceId < raceId || includeCurrentRacePoints) { const pts = (Number(r.points) > 0) ? Number(r.points) : 0; const sprintPts = (r.sprintPoints != null && Number(r.sprintPoints) !== -1) ? Number(r.sprintPoints) : 0; return sum + pts + sprintPts; maxPoints = Math.max(maxPoints, sum + pts + sprintPts); } } return sum; }, 0); // quita la coma y espacio final if (resultsString.endsWith(", ")) resultsString = resultsString.slice(0, -2); if (driverRaces.length > racesNames.length) { racesNames = driverRaces; } // 3) Clasificación de pilotos driverMap[name] = { name: news_insert_space(name), driverId: driverRec.driverId, points: totalDriverPoints, teamId: driverRec.latestTeamId, gapToLeader: 0, }; driversResults.push({ name: news_insert_space(name), resultsString, nPodiums, nWins, teamId: driverRec.latestTeamId, nPointsFinishes }); // 4) Puntos por equipo por carrera races.forEach(r => { const thisRaceId = Number(r.raceId); if (thisRaceId <= raceId) { const teamId = Number(r.teamId); // <-- antes r[r.length-1] const pts = (Number(r.points) > 0) ? Number(r.points) : 0; const sprintPts = (r.sprintPoints != null && Number(r.sprintPoints) !== -1) ? Number(r.sprintPoints) : 0; teamMap[teamId] = (teamMap[teamId] || 0) + pts + sprintPts; } }); }); const driverStandings = Object.values(driverMap).sort((a, b) => b.points - a.points); //calcula gap to leader if (driverStandings.length > 0) { const leaderPoints = driverStandings[0].points; driverStandings.forEach(driver => { driver.gapToLeader = leaderPoints - driver.points; }); } const teamStandings = Object.entries(teamMap) .map(([teamId, points]) => ({ teamId: Number(teamId), points })) .sort((a, b) => b.points - a.points); return { driverStandings, teamStandings, driversResults, racesNames }; } function getLatestChampions(seasonId) { const sql = ` SELECT bas.FirstName, bas.LastName, sta.DriverID, sta.Position, sta.SeasonID, sta.Points FROM Staff_BasicData bas JOIN Races_DriverStandings sta ON bas.StaffID = sta.DriverID WHERE sta.Position <= 3 AND sta.RaceFormula = 1 AND sta.SeasonID < ? `; const rows = queryDB(sql, [seasonId], 'allRows'); let champions = rows.map(row => { const [nameFormatted, driverId] = formatNamesSimple(row); return { name: news_insert_space(nameFormatted), driverId, pos: row[3], season: row[4], points: row[5], }; }); //order champions by season descending and by position ascending champions.sort((a, b) => { if (a.season === b.season) { return a.pos - b.pos; // Ascending order by position } return b.season - a.season; // Descending order by season }); return champions; } function getImagePath(teamId, code, type) { const maxImages = { 'fe': 5, 'mc': 2, 'rb': 4, 'me': 4, 'al': 2, 'wi': 3, 'ha': 2, 'at': 2, 'af': 2, 'as': 2, 'ct': 2, 'f2': 2, 'f3': 2 }; if (type === "raceQuali") { if (teamId === 32) { return `./assets/images/news/${code.toLowerCase()}.webp`; } // Decidir si usar el code o el teamId con 50% de probabilidad const useCode = Math.random() < 0.5; if (useCode) { return `./assets/images/news/${code.toLowerCase()}.webp`; } // Obtener el nombre del equipo desde el diccionario const teamName = team_dict[teamId]; // Obtener el número aleatorio para el archivo const max = maxImages[teamName] || 1; const randomNum = getRandomInt(1, max); return `./assets/images/news/${teamName}${randomNum}.webp`; } else if (type === "transfer") { const useGeneric = Math.random() > 0.5; if (useGeneric) { const randomNum = getRandomInt(1, 12); return `./assets/images/news/con${randomNum}.webp`; } else { return `./assets/images/news/${code}_pad.webp`; } } else if (type === "transfer_generic") { const randomNum = getRandomInt(1, 9); return `./assets/images/news/con${randomNum}.webp`; } else if (type === "champion") { const useChamp = Math.random() < 0.5; if (useChamp) { const randomNum = getRandomInt(1, 5); return `./assets/images/news/champ${randomNum}.webp`; } else { return `./assets/images/news/${code}_tra.webp`; } } else if (type === "reaction") { if (code === "mon"){ return `./assets/images/news/monaco_media.webp`; } const useTrack = Math.random() > 0.8; if (useTrack) { return `./assets/images/news/${code}_tra.webp`; } else { const useMedia = Math.random() < 0.5; if (useMedia) { const randomNum = getRandomInt(1, 5); return `./assets/images/news/${randomNum}_media.webp`; } else { const options = [1, 2, 3, 4, 5, 6, 8, 9, 10] const randomNum = randomPick(options); return `./assets/images/news/${randomNum}_gar.webp`; } } } else if (type === "teamComparison") { return `./assets/images/news/${team_dict[teamId]}_factory.webp`; } else if (type === "season_review") { //number bnetween 1 and 8 included 8 const randomNum = getRandomInt(1, 8); return `./assets/images/news/${randomNum}_shot.webp`; } else if (type === "dsq") { const randomNum = getRandomInt(1, 8); return `./assets/images/news/dsq_${randomNum}.webp`; } else if (type === "technical") { const useGeneric = Math.random() < 0.4; if (useGeneric) { const randomNum = getRandomInt(1, 8); return `./assets/images/news/dsq_${randomNum}.webp`; } else { const randomNum = getRandomInt(1, 3); return `./assets/images/news/part_${code}_${randomNum}.webp`; } } else if (type === "investment") { return `./assets/images/news/${code}_inv.webp`; } else if (type === "race_substitution") { return `./assets/images/news/${code}_tra.webp`; } else if (type === "injury") { return `./assets/images/news/${code}_pad.webp`; } else if (type === "engine"){ const randomNum = getRandomInt(1, 10); return `./assets/images/news/engine_${randomNum}.webp`; } else if (type === "grid"){ const randomNum = getRandomInt(1, 4); return `./assets/images/news/grid_${randomNum}.webp`; } else if (type === "young"){ const randomNum = getRandomInt(1, 9); return `./assets/images/news/young_${randomNum}.webp`; } } export function calculateTeamDropsByDate(season, date) { const excelDate = dateToExcel(date); // cachea el resultado completo de drops para esta fecha exacta const dropKey = `${season}:${excelDate}`; if (_dropsCache.has(dropKey)) return _dropsCache.get(dropKey); const racesDone = fetchEventsDoneBefore(season, excelDate); if (!racesDone.length) { _dropsCache.set(dropKey, []); return []; } const lastRaceId = racesDone[racesDone.length - 1]; const racesCount = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND RaceID <= ?`, [season, lastRaceId], 'singleValue' ); const firstRacePrevSeason = queryDB( `SELECT MIN(RaceID) FROM Races WHERE SeasonID = ?`, [season - 1], 'singleValue' ); const lastYearEquivalent = firstRacePrevSeason + (racesCount - 1); // Usa versiones cacheadas const currentResults = fetchSeasonResultsCached(season); const lastYearResults = fetchSeasonResultsCached(season - 1); const currentStandings = rebuildStandingsUntil(currentResults, lastRaceId); const lastYearStandings = rebuildStandingsUntil(lastYearResults, lastYearEquivalent); const drops = currentStandings.teamStandings.map(team => { const prev = lastYearStandings.teamStandings.find(t => t.teamId === team.teamId); const prevPoints = prev ? prev.points : 0; return { teamId: team.teamId, drop: prevPoints - team.points }; }) _dropsCache.set(dropKey, drops); return drops; } export function getTransferDetails(drivers, date = null) { const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const driverMap = [] drivers.forEach(d => { const previousResultsTeam = queryDB(`SELECT SeasonID, Points, Position FROM Races_TeamStandings WHERE TeamID = ?`, [d.teamId], 'allRows') .map(r => { return { season: r[0], points: r[1], position: r[2] } }); driverMap.push({ driverId: d.driverId, name: d.name, previouslyDrivenTeams: getPreviouslyDrivenTeams(d.driverId), actualTeam: d.team, actualTeamPreviousResults: previousResultsTeam, potentialSalary: d.potentialSalary ? d.potentialSalary : null, potentialTeam: d.potentialTeam ? combined_dict[d.potentialTeam] : null, potentialYearEnd: d.potentialYearEnd ? d.potentialYearEnd : null }) }) let objRace; if (date === null) { const lastRaceIdThisSeason = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? AND State = 2`, [daySeason[1]], 'singleValue'); objRace = lastRaceIdThisSeason } else { objRace = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? AND State = 2 AND Day <= ?`, [daySeason[1], date], 'singleValue'); } const seasonResults = fetchSeasonResults(daySeason[1], true); const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, objRace); const driversForRecords = seasonResults.map(r => ({ id: r.driverId, name: r.driverName ?? "" })); const enrichedAllTime = enrichDriversWithHistory(driversForRecords); return { driverMap, driverStandings, teamStandings, driversResults, racesNames, enrichedAllTime, season: daySeason[1] } } export function getTeamComparisonDetails(teamId, season, date) { const lastRaceBeforeDate = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? AND Day < ?`, [season, date], 'singleValue'); const seasonResults = fetchSeasonResults(season, true); const lastSeasonResults = fetchSeasonResults(season - 1); const { driverStandings: currentDriverStandings, teamStandings: currentTeamStandings, driversResults: currentDriversResults, racesNames: currentRacesNames } = rebuildStandingsUntil(seasonResults, lastRaceBeforeDate, true); const racesCount = queryDB( `SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND RaceID <= ?`, [season, lastRaceBeforeDate], 'singleValue' ); const firstRacePrevSeason = queryDB( `SELECT MIN(RaceID) FROM Races WHERE SeasonID = ?`, [season - 1], 'singleValue' ); const lastYearEquivalent = firstRacePrevSeason + (racesCount - 1); const { driverStandings: oldDriverStandings, teamStandings: oldTeamStandings, driversResults: oldDriversResults, racesNames: oldRacesNames } = rebuildStandingsUntil(lastSeasonResults, lastYearEquivalent, true); const previousResultsTeam = queryDB(`SELECT SeasonID, Points, Position FROM Races_TeamStandings WHERE TeamID = ?`, [teamId], 'allRows') .map(r => { return { season: r[0], points: r[1], position: r[2] } }); const drivers = seasonResults.map(r => ({ id: r.driverId, name: r.driverName ?? "" })); const enrichedAllTime = enrichDriversWithHistory(drivers); return { currentDriverStandings, currentTeamStandings, currentDriversResults, oldDriverStandings, oldTeamStandings, oldDriversResults, currentRacesNames, oldRacesNames, previousResultsTeam, enrichedAllTime }; } export function getFullChampionSeasonDetails(season) { const seasonResults = fetchSeasonResults(season, true); const qualiResults = fetchQualiResults(season); const lastRaceId = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ? AND State = 2`, [season], 'singleValue'); const { driverStandings, teamStandings, driversResults, racesNames } = rebuildStandingsUntil(seasonResults, lastRaceId, true); const { driverStandings: qualiDriverStandings, teamStandings: qualiTeamStandings, driversResults: driverQualiResults, racesNames: qualiRacesNames } = rebuildStandingsUntil(qualiResults, lastRaceId, true, true, true); const champions = getLatestChampions(season); const racesCompleted = queryDB(`SELECT COUNT(*) FROM Races WHERE SeasonID = ? AND State = 2`, [season], 'singleValue'); const drivers = seasonResults.map(r => ({ id: r.driverId, name: r.driverName ?? "" })); const enrichedAllTime = enrichDriversWithHistory(drivers); const isCreateATeam = getGlobals().isCreateATeam; const performances = getPerformanceAllTeamsSeason(isCreateATeam); let performanceEvolution = performances[0].slice(1, -1); const remapped = performanceEvolution.map(obj => { const newObj = {}; Object.entries(obj).forEach(([teamId, value]) => { const teamName = combined_dict[teamId] ?? teamId; // fallback al id newObj[teamName] = value; }); return newObj; }); return { driverStandings, teamStandings, driversResults, driverQualiResults, racesNames, champions, carsPerformance: remapped, enrichedAllTime: enrichedAllTime, } } export function getFullFeederSeriesDetails(season) { const f2 = buildFeederSeriesSeasonDetails(season, 2); const f3 = buildFeederSeriesSeasonDetails(season, 3); return { f2, f3 }; } function buildFeederSeriesSeasonDetails(season, formula) { const seasonResults = fetchSeasonResults(season, true, false, formula) || []; const byDriverId = new Map( seasonResults.map(r => [Number(r.driverId), r]) ); const standingsRows = queryDB(` SELECT DriverID, Points, Position FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = ? ORDER BY Position `, [season, formula], 'allRows') || []; const driverStandings = standingsRows.map(([driverId, points]) => { const id = Number(driverId); const rec = byDriverId.get(id); const rawName = rec?.driverName ?? `Driver ${id}`; return { name: news_insert_space(rawName), driverId: id, points: Number(points) || 0, teamId: Number(rec?.latestTeamId) || 0, gapToLeader: 0, }; }); if (driverStandings.length > 0) { const leaderPoints = driverStandings[0].points; driverStandings.forEach(d => { d.gapToLeader = leaderPoints - d.points; }); } const teamStandingsRows = queryDB(` SELECT TeamID, Points, Position FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = ? ORDER BY Position `, [season, formula], 'allRows') || []; const teamStandings = teamStandingsRows.map(([teamId, points]) => ({ teamId: Number(teamId), points: Number(points) || 0 })); const driversResults = seasonResults.map(driverRec => { const rawName = driverRec?.driverName ?? `Driver ${driverRec?.driverId}`; const name = news_insert_space(rawName); let resultsString = ""; let nPodiums = 0; let nWins = 0; let nPointsFinishes = 0; const races = Array.isArray(driverRec?.races) ? driverRec.races : []; races.forEach(r => { const fin = Number(r.finishingPos); const sprintFin = (r.sprintPos == null) ? null : Number(r.sprintPos); const mainStr = (fin > 0 && fin !== 99) ? `P${fin}` : "DNF"; if (fin === 1) nWins++; if (fin > 0 && fin <= 3) nPodiums++; const featurePts = (Number(r.points) > 0) ? Number(r.points) : 0; const sprintPts = (r.sprintPoints != null && Number(r.sprintPoints) > 0) ? Number(r.sprintPoints) : 0; if ((featurePts + sprintPts) > 0) nPointsFinishes++; let sprintPart = ""; if (sprintFin != null) { const sprintStr = (sprintFin > 0 && sprintFin !== 99) ? `P${sprintFin}` : "DNF"; sprintPart = ` (SPR ${sprintStr})`; } resultsString += `${mainStr}${sprintPart}, `; }); if (resultsString.endsWith(", ")) resultsString = resultsString.slice(0, -2); return { name, resultsString, nPodiums, nWins, teamId: Number(driverRec?.latestTeamId) || 0, nPointsFinishes }; }); return { driverStandings, teamStandings, driversResults, enrichedAllTime: [] }; } export function getPreviouslyDrivenTeams(driverId) { //select distinct from every race that is not raceID 122 or raceID 124 const sql = ` SELECT DISTINCT TeamID, Season FROM Races_Results WHERE DriverID = ? AND RaceID NOT IN (122, 124, 100, 101) ` const rows = queryDB(sql, [driverId], 'allRows'); const teams = rows.map(r => { return { teamId: r[0], season: r[1], teamName: combined_dict[r[0]], } }); return teams; } function buildPointsTable(positionAndPointsRows) { // rows: [RacePos, Points] const tbl = new Map(); for (const [pos, pts] of positionAndPointsRows) { tbl.set(Number(pos), Number(pts) || 0); } return tbl; } function getBasePointsForPos(pos, pointsTable, doublePoints, isLastRace = false) { const base = pointsTable.get(pos) ?? 0; return doublePoints && isLastRace ? base * 2 : base; } // ---- Fastest Lap (FastestLap en segundos) ---- function getFastestLapHolderBySeconds(raceId, queryDB) { const row = queryDB(` SELECT DriverID FROM Races_Results WHERE RaceID = ? AND FastestLap IS NOT NULL AND FastestLap > 0 ORDER BY FastestLap ASC LIMIT 1 `, [raceId], 'singleRow'); return row ? Number(row[0]) : null; } export function checkDoublePointsBug(turningPointState){ const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); let wasBugged = {result : false, raceId: null}; const ilegalRaces = turningPointState.ilegalRaces if (ilegalRaces.length === 0) return wasBugged; for (let i = 0; i < ilegalRaces.length; i++) { let raceId = ilegalRaces[i].race_id; let winnerRow = queryDB(` SELECT DriverID, Points FROM Races_Results WHERE RaceID = ? AND FinishingPos = 1 `, [raceId], 'singleRow'); let winnerRowPrevRace = queryDB(` SELECT DriverID, Points FROM Races_Results WHERE RaceID = ? AND FinishingPos = 1 AND Season = ? `, [raceId - 1, daySeason[1]], 'singleRow'); //if it doesnt existe then take the next race if (!winnerRowPrevRace) { winnerRowPrevRace = queryDB(` SELECT DriverID, Points FROM Races_Results WHERE RaceID = ? AND FinishingPos = 1 AND Season = ? `, [raceId + 1, daySeason[1]], 'singleRow'); } //if points are more than double, then bug happened if (winnerRow && winnerRowPrevRace) { if (Number(winnerRow[1]) >= Number(winnerRowPrevRace[1]) * 2) { wasBugged = {result : true, raceId: raceId}; return wasBugged; } } } return wasBugged; } export function fixDoublePointsBug(raceId) { const rows = queryDB(` SELECT DriverID, Points FROM Races_Results WHERE RaceID = ? AND Points > 0 `, [raceId], 'allRows'); for (let i = 0; i < rows.length; i++) { let driverId = rows[i][0]; let champPoints = Number(rows[i][1]); let fixedPoints = Math.floor(champPoints / 2); queryDB(` UPDATE Races_Results SET Points = ? WHERE RaceID = ? AND DriverID = ? `, [fixedPoints, raceId, driverId], 'run'); } } /** * Descalifica al equipo y recalcula todo lo necesario (pilotos + constructores + vuelta rápida). * * @param {Object} params * @param {number} params.raceId * @param {number} params.teamId * @param {Function} params.queryDB * @param {Object} params.pointsReg - objeto de fetchPointsRegulations() */ function disqualifyTeamInRace({ raceId, teamId, queryDB, pointsReg, }) { // 0) Contexto temporada const daySeason = queryDB(`SELECT Day, CurrentSeason FROM Player_State`, [], 'singleRow'); const seasonId = daySeason?.[1]; // 1) Config de puntos const pointsTable = buildPointsTable(pointsReg.positionAndPoints); const flEnabled = Number(pointsReg.fastestLapBonusPoint) === 1; const doublePts = pointsReg.isLastraceDouble ? true : false; //falta añadir que si la regulacion esta activa Y ES LA ULTIMA CARRERA // 2) Estado inicial de la carrera const allRes = queryDB(` SELECT DriverID, TeamID, FinishingPos, Points, IFNULL(DNF,0) AS DNF, FastestLap FROM Races_Results WHERE RaceID = ? ORDER BY FinishingPos ASC `, [raceId], 'allRows') || []; if (!allRes.length) return; const prevRacePoints = new Map(); // DriverID -> puntos antes const driverTeam = new Map(); // DriverID -> TeamID for (const r of allRes) { prevRacePoints.set(Number(r[0]), Number(r[3]) || 0); driverTeam.set(Number(r[0]), Number(r[1])); } // Identifica DSQ del teamId const dsqRows = allRes.filter(r => Number(r[1]) === Number(teamId)); if (!dsqRows.length) return; const dsqIds = new Set(dsqRows.map(r => Number(r[0]))); // 1) Offset temporal a TODA la carrera para no violar UNIQUE queryDB(` UPDATE Races_Results SET FinishingPos = FinishingPos + 1000 WHERE RaceID = ? `, [raceId], 'run'); // 2) Construye el nuevo orden completo const classified = allRes .filter(r => r[4] === 0 && !dsqIds.has(Number(r[0]))) .sort((a, b) => a[2] - b[2]); // por FinishingPos original const dnfsOther = allRes .filter(r => r[4] === 1 && !dsqIds.has(Number(r[0]))) .sort((a, b) => a[2] - b[2]); // conserva su orden relativo const dsqSorted = dsqRows.slice().sort((a, b) => a[2] - b[2]); // el que estaba más arriba primero // 3) Reasignar FinishingPos secuencialmente y puntos base let pos = 1; const afterRacePoints = new Map(); // DriverID -> puntos tras recálculo (sin FL aún) const lastRace = queryDB(`SELECT MAX(RaceID) FROM Races WHERE SeasonID = ?`, [seasonId], 'singleValue'); const isLastRace = Number(raceId) === Number(lastRace); // Clasificados: posiciones 1..K + puntos for (const r of classified) { const driverId = Number(r[0]); const pts = getBasePointsForPos(pos, pointsTable, doublePts, isLastRace); afterRacePoints.set(driverId, pts); queryDB(` UPDATE Races_Results SET FinishingPos = ?, Points = ?, DNF = 0 WHERE RaceID = ? AND DriverID = ? `, [pos, pts, raceId, driverId], 'run'); pos++; } // DNFs no DSQ: van detrás de clasificados, sin puntos for (const r of dnfsOther) { const driverId = Number(r[0]); afterRacePoints.set(driverId, 0); queryDB(` UPDATE Races_Results SET FinishingPos = ?, Points = 0, DNF = 1 WHERE RaceID = ? AND DriverID = ? `, [pos, raceId, driverId], 'run'); pos++; } // DSQ del equipo: los dos últimos for (let i = 0; i < dsqSorted.length; i++) { const driverId = Number(dsqSorted[i][0]); afterRacePoints.set(driverId, 0); queryDB(` UPDATE Races_Results SET FinishingPos = ?, Points = 0, DNF = 1 WHERE RaceID = ? AND DriverID = ? `, [pos, raceId, driverId], 'run'); pos++; } // 4) BONUS VUELTA RÁPIDA (si habilitado y el portador queda top-10 y DNF=0) if (flEnabled) { const flDriver = getFastestLapHolderBySeconds(raceId, queryDB); if (flDriver != null && !dsqIds.has(flDriver)) { const row = queryDB(` SELECT FinishingPos, IFNULL(DNF,0) FROM Races_Results WHERE RaceID = ? AND DriverID = ? `, [raceId, flDriver], 'singleRow'); const fPos = Number(row?.[0] ?? 9999); const fDNF = Number(row?.[1] ?? 0); if (fDNF === 0 && fPos <= 10) { const bonus = 1; const base = afterRacePoints.get(flDriver) ?? 0; const withBonus = base + bonus; afterRacePoints.set(flDriver, withBonus); queryDB(` UPDATE Races_Results SET Points = ? WHERE RaceID = ? AND DriverID = ? `, [withBonus, raceId, flDriver], 'run'); } } } if (Number(pointsReg.poleBonusPoint) === 1) { const poleDriverId = queryDB(` SELECT DriverID FROM Races_Results WHERE RaceID = ? AND StartingPos = 1 `, [raceId], 'singleValue'); if (poleDriverId != null) { const poleId = Number(poleDriverId); if (!dsqIds.has(poleId)) { // Si tu normativa NO duplica la pole, fija bonus = 1. const bonus = 1; const base = afterRacePoints.get(poleId) ?? 0; const withBonus = base + bonus; afterRacePoints.set(poleId, withBonus); queryDB(` UPDATE Races_Results SET Points = ? WHERE RaceID = ? AND DriverID = ? `, [withBonus, raceId, poleId], 'run'); } } } // 5) Deltas campeonato (pilotos) + acumulado por equipo (constructores) const teamDelta = new Map(); // TeamID -> delta total for (const r of allRes) { const driverId = Number(r[0]); const team = driverTeam.get(driverId); const before = prevRacePoints.get(driverId) || 0; const after = afterRacePoints.get(driverId) ?? 0; const delta = after - before; if (delta !== 0 && seasonId != null) { queryDB(` UPDATE Races_DriverStandings SET Points = Points + ? WHERE SeasonID = ? AND DriverID = ? `, [delta, seasonId, driverId], 'run'); } teamDelta.set(team, (teamDelta.get(team) || 0) + delta); } if (seasonId != null) { for (const [team, delta] of teamDelta.entries()) { if (delta !== 0) { queryDB(` UPDATE Races_TeamStandings SET Points = Points + ? WHERE SeasonID = ? AND TeamID = ? `, [delta, seasonId, team], 'run'); } } queryDB(` WITH ranked AS ( SELECT DriverID, ROW_NUMBER() OVER (PARTITION BY SeasonID ORDER BY Points DESC, DriverID ASC) AS pos FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = 1 ) UPDATE Races_DriverStandings SET Position = ( SELECT pos FROM ranked WHERE ranked.DriverID = Races_DriverStandings.DriverID AND RaceFormula = 1 ) WHERE SeasonID = ? AND RaceFormula = 1; `, [seasonId, seasonId], 'run'); // Recalcular posición en la clasificación de constructores queryDB(` WITH ranked AS ( SELECT TeamID, ROW_NUMBER() OVER (PARTITION BY SeasonID ORDER BY Points DESC, TeamID ASC) AS pos FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = 1 ) UPDATE Races_TeamStandings SET Position = ( SELECT pos FROM ranked WHERE ranked.TeamID = Races_TeamStandings.TeamID AND RaceFormula = 1 ) WHERE SeasonID = ? AND RaceFormula = 1; `, [seasonId, seasonId], 'run'); } } function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } // SAVING NEWS const encodeJSON = (obj) => USE_COMPRESSION ? LZString.compressToUTF16(JSON.stringify(obj)) : JSON.stringify(obj); const decodeJSON = (txt) => { if (!txt) return {}; const raw = USE_COMPRESSION ? LZString.decompressFromUTF16(txt) : txt; try { return JSON.parse(raw || "{}"); } catch { return {}; } }; // --- DB helpers --- export function ensureEditorStateTable() { const exists = queryDB( `SELECT name FROM sqlite_master WHERE type='table' AND name='Custom_News_State'`, [], "singleRow" ); if (!exists) queryDB(`CREATE TABLE Custom_News_State (key TEXT PRIMARY KEY, value TEXT)`, [], 'run'); } export function getEditorState(key) { ensureEditorStateTable(); const row = queryDB(`SELECT value FROM Custom_News_State WHERE key = ?`, [key], "singleRow"); return row ? row[0] : null; } export function setEditorState(key, valueText) { ensureEditorStateTable(); queryDB(` INSERT INTO Custom_News_State (key,value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value `, [key, valueText], 'run'); } // --- computeStableKey compartido (mismo algoritmo en front y worker) --- export function computeStableKey(n) { if (n.id != null && n.id !== "") return String(n.id); // ajusta por tipo si quieres; fallback seguro: return `h:${n.type}|${n.title}|${n.date}`; } // --- NEWS: load/save/upsert --- export function loadNewsMapFromDB(season = null) { const globals = getGlobals(); if (season == null) { season = globals?.currentDate[1]; } const key = `${season}_news`; return decodeJSON(getEditorState(key)) || {}; } export function saveNewsToDBMap(map) { const globals = getGlobals(); const year = globals?.currentDate[1] const key = `${year}_news`; setEditorState(key, encodeJSON(map)); } export function deleteNews() { const globals = getGlobals(); const year = globals?.currentDate[1] const key = `${year}_news`; setEditorState(key, encodeJSON({})); } export function deleteTurningPoints() { const globals = getGlobals(); const year = globals?.currentDate[1] const key = `${year}_turning_points`; setEditorState(key, encodeJSON({})); } export function upsertNews(newsList = []) { const prev = loadNewsMapFromDB(); const out = { ...prev }; for (const n of newsList) { const key = n.stableKey ?? computeStableKey(n); const old = out[key] || {}; out[key] = { ...old, title: n.title, type: n.type, date: n.date, image: n.image, overlay: n.overlay, data: n.data, text: n.text ?? old.text, turning_point_type: n.turning_point_type ?? old.turning_point_type, nonReadable: n.nonReadable ?? old.nonReadable, hiddenByAvailability: n.hiddenByAvailability ?? old.hiddenByAvailability, hiddenReason: n.hiddenReason ?? old.hiddenReason, stableKey: key, }; } saveNewsToDBMap(out); return out; // por si quieres devolver el estado } // --- TP: load/save/upsert (simple: arrays -> replace, objetos -> merge, primitivos -> replace) --- export function loadTPFromDB(season = null) { const globals = getGlobals(); if (season == null) { season = globals?.currentDate[1]; } const key = `${season}_turning_points`; return decodeJSON(getEditorState(key)) || {}; } export function saveTPToDBMap(map) { const globals = getGlobals(); const year = globals?.currentDate[1] const key = `${year}_turning_points`; setEditorState(key, encodeJSON(map)); } export function upsertTurningPoints(tpPartial = {}) { const prev = loadTPFromDB(); const out = { ...prev }; for (const [k, v] of Object.entries(tpPartial)) { const old = out[k]; if (Array.isArray(v)) out[k] = v.slice(); else if (v && typeof v === "object") out[k] = { ...(old || {}), ...v }; else out[k] = v; } saveTPToDBMap(out); return out; } export function updateNewsFields(stableKey, patch) { const map = loadNewsMapFromDB(); const old = map[stableKey] || null; if (!old) return false; // o lanzar error si prefieres // Solo actualiza los campos permitidos const allowed = ["text", "turning_point_type", "nonReadable", "hiddenByAvailability", "hiddenReason", "overlay", "image", "title"]; const next = { ...old }; for (const k of allowed) { if (patch[k] !== undefined) next[k] = patch[k]; } map[stableKey] = next; saveNewsToDBMap(map); return true; } export function deleteNewByKey(stableKey) { const map = loadNewsMapFromDB(); if (map[stableKey]) { delete map[stableKey]; saveNewsToDBMap(map); return true; } return false; } export function isMigrationDone() { return getEditorState("_migration_v1_done") === "1"; } export function markMigrationDone() { setEditorState("_migration_v1_done", "1"); } // util segura function safeParse(txt, fallback) { try { return JSON.parse(txt); } catch { return fallback; } } function mergeNewsMaps(dbMap, lsMap) { // preferimos LS para no perder artículos generados/ediciones locales return { ...dbMap, ...lsMap }; } function mergeTPMaps(dbMap, lsMap) { const out = { ...dbMap }; for (const [k, v] of Object.entries(lsMap)) { const old = out[k]; if (Array.isArray(v)) out[k] = v.slice(); // arrays -> replace else if (v && typeof v === "object") out[k] = { ...(old || {}), ...v }; // objetos -> merge else out[k] = v; // primitivos/null -> replace } return out; } export function migrateLegacyData(lsNewsTxt, lsTPTxt) { if (isMigrationDone()) return "already"; // idempotente if (lsNewsTxt) { const lsNewsMap = safeParse(lsNewsTxt, {}); const dbNewsMap = loadNewsMapFromDB(); const outNews = mergeNewsMaps(dbNewsMap, lsNewsMap); saveNewsToDBMap(outNews); } if (lsTPTxt) { const lsTPMap = safeParse(lsTPTxt, {}); const dbTPMap = loadTPFromDB(); const outTP = mergeTPMaps(dbTPMap, lsTPMap); saveTPToDBMap(outTP); } markMigrationDone(); return "migrated"; } export function ensureTurningPointsStructure() { const globals = getGlobals?.(); // si lo tienes disponible const year = globals?.currentDate?.[1]; const key = `${year}_tps`; let data = loadTPFromDB(); if (data && Object.keys(data).length > 0) { return data; } // Estructura por defecto const defaultStructure = { checkedRaces: [], ilegalRaces: [], transfers: { 5: null, 6: null, 7: null }, technicalDirectives: { 6: null, 9: null }, investmentOpportunities: { 4: null, 5: null, 6: null, 7: null, 8: null, 9: null, 10: null, 11: null }, raceSubstitutionOpportunities: { 4: null, 5: null, 6: null, 7: null, 8: null, 9: null, 10: null, 11: null }, youngDrivers: null }; // guarda en DB si no existía saveTPToDBMap(defaultStructure); return defaultStructure; } export function getNewsAndTpYearsAvailable() { const minYear = isTimeTravel2026Enabled() ? 2026 : 0; const yearsSet = new Set(); const editorStateRows = queryDB( `SELECT key FROM Custom_News_State WHERE key LIKE '%_news' OR key LIKE '%_turning_points'`, [], 'allRows' ); for (const [key] of editorStateRows) { const match = key.match(/^(\d{4})_(news|turning_points)$/); if (match) { const year = Number(match[1]); if (year >= minYear) { yearsSet.add(year); } } } const years = Array.from(yearsSet); years.sort((a, b) => b - a); return years; } export function getNewsFromSeason(season) { const newsMap = loadNewsMapFromDB(season); const tpMap = loadTPFromDB(season); return { newsList: Object.values(newsMap).sort((a, b) => new Date(b.date) - new Date(a.date)), turningPointState: tpMap }; } export function startInjurySwap(injuredId, reserveData, endDay) { const [dayNow, seasonId] = queryDB(` SELECT Day, CurrentSeason FROM Player_State `, [], 'singleRow'); let reserveId = reserveData.id; if (reserveData.isFreeAgent) { let teamId = reserveData.futureTeamId hireDriver("auto", reserveId, teamId, 10); } swapDrivers(injuredId, reserveId); return { seasonId, dayNow }; } function applyDriverInjury(turningPointData) { // 1) Aplicar lesión y registrar const injuredId = turningPointData.driver_affected.id; const reserveData = turningPointData.reserve_driver const endDay = turningPointData.condition.end_date; let reserveId = reserveData.id; const { seasonId } = startInjurySwap(injuredId, reserveData, endDay); } ================================================ FILE: src/js/backend/scriptUtils/recordUtils.js ================================================ import { queryDB, setMetaData, getMetadata } from "../dbManager.js"; import { formatNamesSimple, fetchDriverOfTheDayCounts, fetchDriversStandings, fetchTeamsStandingsWithPoints, fetchTeamMateQualiRaceHeadToHead, fetchTeamSeasonPodiumsTotals, fetchTeamSeasonPolesTotals, fetchTeamSeasonWinsTotals, fetchEventsFrom, fetchPointsRegulations, fetchLastCompletedRaceId, ensureCustomDoDRankingTable } from "./dbUtils.js"; import records from "../../../data/records.json"; import { getGlobals } from "../commandGlobals.js"; function idsToCsv(ids) { return Array.from(new Set(ids)).filter(x => x != null).join(","); } function fetchHistoryMap(tableName, ids) { if (!ids.length) return new Map(); const placeholders = ids.map(() => '?').join(','); const rows = queryDB(` SELECT StaffID, TotalStarts, FirstRace AS FirstRaceSeason, FirstRaceTrackID, FirstPodium AS FirstPodiumSeason, FirstPodiumTrackID, FirstWin AS FirstWinSeason, FirstWinTrackID, LastWin AS LastWinSeason, LastWinTrackID, TotalPoles, TotalPodiums, TotalWins, TotalSprintWins, TotalChampionshipWins, TotalPointsScored, TotalFastestLaps FROM ${tableName} WHERE StaffID IN (${placeholders}) AND Formula = 1 `, ids, 'allRows') || []; const map = new Map(); for (const r of rows) { map.set(r[0], { totalStarts: r[1] ?? 0, firstRace: { season: r[2], trackId: r[3] }, firstPodium: { season: r[4], trackId: r[5] }, firstWin: { season: r[6], trackId: r[7] }, lastWin: { season: r[8], trackId: r[9] }, totalPoles: r[10] ?? 0, totalPodiums: r[11] ?? 0, totalWins: r[12] ?? 0, totalSprintWins: r[13] ?? 0, totalChampionshipWins: r[14] ?? 0, totalPointsScored: r[15] ?? 0, totalFastestLaps: r[16] ?? 0 }); } return map; } export function fetchDriverHistoryRecords(historyTable, ids, season) { const map = new Map(); if (!ids || !ids.length) return map; const placeholders = ids.map(() => '?').join(','); const historyRows = queryDB(` SELECT StaffID, TotalStarts, FirstRace AS FirstRaceSeason, FirstRaceTrackID, FirstPodium AS FirstPodiumSeason, FirstPodiumTrackID, FirstWin AS FirstWinSeason, FirstWinTrackID, LastWin AS LastWinSeason, LastWinTrackID, TotalPoles, TotalPodiums, TotalWins, TotalSprintWins, TotalChampionshipWins, TotalPointsScored, TotalFastestLaps FROM ${historyTable} WHERE StaffID IN (${placeholders}) AND Formula = 1 `, ids, 'allRows') || []; for (const r of historyRows) { map.set(r[0], { totalStarts: r[1] ?? 0, firstRace: { season: r[2] ?? null, trackId: r[3] ?? null }, firstPodium: { season: r[4] ?? null, trackId: r[5] ?? null }, firstWin: { season: r[6] ?? null, trackId: r[7] ?? null }, lastWin: { season: r[8] ?? null, trackId: r[9] ?? null }, totalPoles: r[10] ?? 0, totalPodiums: r[11] ?? 0, totalWins: r[12] ?? 0, totalSprintWins: r[13] ?? 0, totalChampionshipWins: r[14] ?? 0, totalPointsScored: r[15] ?? 0, totalFastestLaps: r[16] ?? 0, }); } for (const id of ids) { if (!map.has(id)) { map.set(id, { totalStarts: 0, firstRace: { season: null, trackId: null }, firstPodium: { season: null, trackId: null }, firstWin: { season: null, trackId: null }, lastWin: { season: null, trackId: null }, totalPoles: 0, totalPodiums: 0, totalWins: 0, totalSprintWins: 0, totalChampionshipWins: 0, totalPointsScored: 0, totalFastestLaps: 0 }); } } if (season != null) { const seasonRows = queryDB(` SELECT StaffID, TotalPoles, TotalPodiums, TotalWins, TotalSprintWins, TotalChampionshipWins, TotalPointsScored, TotalFastestLaps, TotalStarts FROM Staff_Driver_RaceRecordPerSeason WHERE StaffID IN (${placeholders}) AND SeasonID = ? `, [...ids, season], 'allRows') || []; for (const r of seasonRows) { const id = r[0]; const base = map.get(id) || { totalStarts: 0, firstRace: { season: null, trackId: null }, firstPodium: { season: null, trackId: null }, firstWin: { season: null, trackId: null }, lastWin: { season: null, trackId: null }, totalPoles: 0, totalPodiums: 0, totalWins: 0, totalSprintWins: 0, totalChampionshipWins: 0, totalPointsScored: 0, totalFastestLaps: 0, totalStarts: 0 }; base.totalPoles = r[1] ?? 0; base.totalPodiums = r[2] ?? 0; base.totalWins = r[3] ?? 0; base.totalSprintWins = r[4] ?? 0; base.totalChampionshipWins = r[5] ?? 0; base.totalPointsScored = r[6] ?? 0; base.totalFastestLaps = r[7] ?? 0; base.totalStarts = r[8] ?? 0; map.set(id, base); } } return map; } function validSeason(x) { return x && x.season && x.season !== 0; } export function enrichDriversWithHistory(drivers, season = null) { if (!drivers || drivers.length === 0) return drivers; const ids = drivers.map(d => d.id); const BEFORE = "Staff_Driver_RaceRecordBeforeGameStart"; const SINCE = "Staff_Driver_RaceRecordSinceGameStart"; const bMap = fetchDriverHistoryRecords(BEFORE, ids, season); const sMap = fetchDriverHistoryRecords(SINCE, ids, season); return drivers.map(d => { const b = bMap.get(d.id) || {}; const s = sMap.get(d.id) || {}; const firstRace = validSeason(b.firstRace) ? b.firstRace : (validSeason(s.firstRace) ? s.firstRace : { season: 0, trackId: null }); const firstPodium = validSeason(b.firstPodium) ? b.firstPodium : (validSeason(s.firstPodium) ? s.firstPodium : { season: 0, trackId: null }); const firstWin = validSeason(b.firstWin) ? b.firstWin : (validSeason(s.firstWin) ? s.firstWin : { season: 0, trackId: null }); const lastWin = validSeason(s.lastWin) ? s.lastWin : (validSeason(b.lastWin) ? b.lastWin : { season: 0, trackId: null }); let totalPoles, totalPodiums, totalWins, totalSprintWins, totalChampionshipWins, totalPointsScored, totalFastestLaps, totalStarts; if (!season) { totalPoles = (b.totalPoles ?? 0) + (s.totalPoles ?? 0); totalPodiums = (b.totalPodiums ?? 0) + (s.totalPodiums ?? 0); totalWins = (b.totalWins ?? 0) + (s.totalWins ?? 0); totalSprintWins = (b.totalSprintWins ?? 0) + (s.totalSprintWins ?? 0); totalChampionshipWins = (b.totalChampionshipWins ?? 0) + (s.totalChampionshipWins ?? 0); totalPointsScored = (b.totalPointsScored ?? 0) + (s.totalPointsScored ?? 0); totalFastestLaps = (b.totalFastestLaps ?? 0) + (s.totalFastestLaps ?? 0); totalStarts = (b.totalStarts ?? 0) + (s.totalStarts ?? 0); } else { totalPoles = s.totalPoles ?? 0; totalPodiums = s.totalPodiums ?? 0; totalWins = s.totalWins ?? 0; totalSprintWins = s.totalSprintWins ?? 0; totalChampionshipWins = s.totalChampionshipWins ?? 0; totalPointsScored = s.totalPointsScored ?? 0; totalFastestLaps = s.totalFastestLaps ?? 0; totalStarts = s.totalStarts ?? 0; } return { ...d, totalStarts, firstRace, firstPodium, firstWin, lastWin, totalPoles, totalPodiums, totalWins, totalSprintWins, totalChampionshipWins, totalPointsScored, totalFastestLaps }; }); } export function getSelectedRecord(type, year) { let recordTargetColumn, recordTargetTable; if (type === "dotd") { if (year === "all") { ensureCustomDoDRankingTable(); const rows = queryDB(` SELECT bas.FirstName, bas.LastName, t.DriverID, COUNT(*) AS Count, COALESCE(( SELECT w.TeamID FROM Custom_DriverOfTheDay_Ranking w JOIN Races r ON r.RaceID = w.RaceID WHERE w.Rank = 1 AND w.DriverID = t.DriverID ORDER BY w.Season DESC, r.Day DESC, w.RaceID DESC LIMIT 1 ), -1) AS TeamID, COALESCE(gam.Retired, 0) AS Retired FROM Custom_DriverOfTheDay_Ranking t JOIN Staff_BasicData bas ON bas.StaffID = t.DriverID LEFT JOIN Staff_GameData gam ON gam.StaffID = t.DriverID WHERE t.Rank = 1 GROUP BY t.DriverID ORDER BY Count DESC `, [], 'allRows') || []; const formatted = rows.map(r => ({ name: formatNamesSimple([r[0], r[1]])[0], id: r[2], record: type, value: r[3], teamId: r[4], retired: r[5], })); return enrichDriversWithHistory(formatted); } const counts = fetchDriverOfTheDayCounts(year) || []; const formatted = counts.map(r => ({ name: r.name, id: r.id, record: type, value: r.count, teamId: r.teamId, retired: 0, })).sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); return enrichDriversWithHistory(formatted, year); } if (type === "wins") recordTargetColumn = "TotalWins"; else if (type === "podiums") recordTargetColumn = "TotalPodiums"; else if (type === "poles") recordTargetColumn = "TotalPoles"; else if (type === "champs") recordTargetColumn = "TotalChampionshipWins"; else if (type === "fastestlaps") recordTargetColumn = "TotalFastestLaps"; else if (type === "points") recordTargetColumn = "TotalPointsScored"; else if (type === "races") recordTargetColumn = "TotalStarts"; if (year === "all") { const recordTargetTableBefore = "Staff_Driver_RaceRecordBeforeGameStart"; const recordTargetTableSince = "Staff_Driver_RaceRecordSinceGameStart"; const beforeGameStart = queryDB(` SELECT bas.FirstName, bas.LastName, tab1.StaffID, tab1.${recordTargetColumn}, COALESCE(con.TeamID, -1) AS TeamID, gam.Retired FROM ${recordTargetTableBefore} tab1 JOIN Staff_BasicData bas ON tab1.StaffID = bas.StaffID LEFT JOIN Staff_Contracts con ON tab1.StaffID = con.StaffID AND con.ContractType = 0 LEFT JOIN Staff_GameData gam ON tab1.StaffID = gam.StaffID WHERE tab1.Formula = 1 AND tab1.${recordTargetColumn} IS NOT 0 ORDER BY tab1.${recordTargetColumn} DESC `, [], 'allRows'); const sinceGameStart = queryDB(` SELECT bas.FirstName, bas.LastName, tab1.StaffID, tab1.${recordTargetColumn}, COALESCE(con.TeamID, -1) AS TeamID, gam.Retired FROM ${recordTargetTableSince} tab1 JOIN Staff_BasicData bas ON tab1.StaffID = bas.StaffID LEFT JOIN Staff_Contracts con ON tab1.StaffID = con.StaffID AND con.ContractType = 0 LEFT JOIN Staff_GameData gam ON tab1.StaffID = gam.StaffID WHERE tab1.Formula = 1 AND tab1.${recordTargetColumn} IS NOT 0 ORDER BY tab1.${recordTargetColumn} DESC `, [], 'allRows'); const formattedBefore = beforeGameStart.map(r => ({ name: formatNamesSimple([r[0], r[1]])[0], id: r[2], record: type, value: r[3], teamId: r[4], retired: r[5], })); const formattedSince = sinceGameStart.map(r => ({ name: formatNamesSimple([r[0], r[1]])[0], id: r[2], record: type, value: r[3], teamId: r[4], retired: r[5], })); const byId = {}; formattedBefore.forEach(r => { byId[r.id] = { ...r }; }); formattedSince.forEach(r => { if (byId[r.id]) byId[r.id].value += r.value; else byId[r.id] = { ...r }; }); const combinedArray = Object.values(byId).sort((a, b) => b.value - a.value); // enriquecemos aquí const enriched = enrichDriversWithHistory(combinedArray); const ALLTIME_EXTERNAL_DRIVERS = records; const merged = mergeWithExternalRecords(enriched, ALLTIME_EXTERNAL_DRIVERS, type, year); //sort again after merging merged.sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); console.log("final all-time records:", merged); return merged; } else { // temporada concreta recordTargetTable = "Staff_Driver_RaceRecordPerSeason"; const record = queryDB(` SELECT bas.FirstName, bas.LastName, tab1.StaffID, tab1.${recordTargetColumn}, -- Team en la ÚLTIMA carrera de esa temporada COALESCE(( SELECT rr.TeamID FROM Races_Results rr JOIN Races r ON r.RaceID = rr.RaceID WHERE rr.DriverID = tab1.StaffID AND r.SeasonID = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID, COALESCE(gam.Retired, 0) AS Retired FROM Staff_Driver_RaceRecordPerSeason tab1 JOIN Staff_BasicData bas ON tab1.StaffID = bas.StaffID LEFT JOIN Staff_GameData gam ON tab1.StaffID = gam.StaffID WHERE tab1.SeasonID = ? AND tab1.${recordTargetColumn} IS NOT 0 AND (tab1.TeamID <= 10 OR tab1.TeamID = 32) ORDER BY tab1.${recordTargetColumn} DESC `, [year, year], 'allRows'); const formatted = (record || []).map(r => ({ name: formatNamesSimple([r[0], r[1]])[0], id: r[2], record: type, value: r[3], teamId: r[4], retired: r[5], })); // enriquecemos también para temporada concreta con histórico all-time const aggregated = aggregateSeasonDriverRecords(formatted); return enrichDriversWithHistory(aggregated, year); } } export function fetchSeasonReviewData(year, formula = 1, isCurrentYear = true) { const teamsStandings = fetchTeamsStandingsWithPoints(year, formula); const driversStandings = fetchDriversStandings(year, formula); const events = fetchEventsFrom(year, formula); const pointsInfo = fetchPointsRegulations(); const lastRaceDoneId = fetchLastCompletedRaceId(year, formula); const winsRecords = getSelectedRecord("wins", year); const polesRecords = getSelectedRecord("poles", year); const podiumsRecords = getSelectedRecord("podiums", year); const teamWinsTotals = fetchTeamSeasonWinsTotals(year, formula); const teamPolesTotals = fetchTeamSeasonPolesTotals(year, formula); const teamPodiumsTotals = fetchTeamSeasonPodiumsTotals(year, formula); const qualifyingStageCounts = fetchQualifyingStageCounts(year, formula, isCurrentYear); const driverOfTheDayCounts = fetchDriverOfTheDayCounts(year); const teamMateHeadToHead = fetchTeamMateQualiRaceHeadToHead(year); return { year, formula, teamsStandings, driversStandings, events, pointsInfo, lastRaceDoneId, qualifyingStageCounts, driverOfTheDayCounts, teamMateHeadToHead, teamWinsTotals, teamPolesTotals, teamPodiumsTotals, polesRecords, podiumsRecords, winsRecords }; } export function getSelectedTeamRecord(type, year, formula = 1) { if (!type) return []; if (String(year) === "all") return []; const recordType = String(type); let base = []; let breakdownRows = []; if (recordType === "wins") base = fetchTeamSeasonWinsTotals(year, formula); else if (recordType === "podiums") base = fetchTeamSeasonPodiumsTotals(year, formula); else if (recordType === "poles") base = fetchTeamSeasonPolesTotals(year, formula); else if (recordType === "dotd") { if (Number(formula) !== 1) return []; ensureCustomDoDRankingTable(); const globals = getGlobals(); const teamFilterSql = globals.isCreateATeam ? `(t.TeamID BETWEEN 1 AND 10 OR t.TeamID = 32)` : `(t.TeamID BETWEEN 1 AND 10)`; const rows = queryDB(` SELECT t.TeamID, COUNT(*) AS Cnt FROM Custom_DriverOfTheDay_Ranking t WHERE t.Season = ? AND t.Rank = 1 AND ${teamFilterSql} GROUP BY t.TeamID ORDER BY Cnt DESC, t.TeamID ASC `, [year], 'allRows') || []; base = rows.map(r => ({ teamId: Number(r[0]), value: Number(r[1]) || 0, })); breakdownRows = queryDB(` SELECT t.TeamID, t.DriverID, bas.FirstName, bas.LastName, COUNT(*) AS Cnt FROM Custom_DriverOfTheDay_Ranking t JOIN Staff_BasicData bas ON bas.StaffID = t.DriverID WHERE t.Season = ? AND t.Rank = 1 AND ${teamFilterSql} GROUP BY t.TeamID, t.DriverID ORDER BY t.TeamID ASC, Cnt DESC `, [year], 'allRows') || []; } else { return []; } const breakdownByTeamId = new Map(); if (recordType === "wins" || recordType === "podiums" || recordType === "poles") { if (Number(formula) !== 1) return base; const globals = getGlobals(); const teamFilterSql = globals.isCreateATeam ? `(rr.TeamID BETWEEN 1 AND 10 OR rr.TeamID = 32)` : `(rr.TeamID BETWEEN 1 AND 10)`; let whereSql = ""; if (recordType === "wins") whereSql = `rr.FinishingPos = 1`; else if (recordType === "podiums") whereSql = `rr.FinishingPos BETWEEN 1 AND 3`; else whereSql = `rr.StartingPos = 1`; breakdownRows = queryDB(` SELECT rr.TeamID, rr.DriverID, bas.FirstName, bas.LastName, COUNT(*) AS Cnt FROM Races_Results rr JOIN Staff_BasicData bas ON bas.StaffID = rr.DriverID WHERE rr.Season = ? AND ${teamFilterSql} AND ${whereSql} GROUP BY rr.TeamID, rr.DriverID ORDER BY rr.TeamID ASC, Cnt DESC `, [year], 'allRows') || []; } breakdownRows.forEach((r) => { const teamId = Number(r[0]); const driverId = Number(r[1]); const count = Number(r[4]) || 0; if (count <= 0) return; const name = formatNamesSimple([r[2], r[3]])[0]; if (!breakdownByTeamId.has(teamId)) breakdownByTeamId.set(teamId, []); breakdownByTeamId.get(teamId).push({ id: driverId, name, count }); }); const lastRaceId = Number(formula) === 1 ? queryDB(` SELECT RaceID FROM Races WHERE SeasonID = ? AND State = 2 ORDER BY Day DESC, RaceID DESC LIMIT 1 `, [year], 'singleValue') : null; const attachDriversAndBreakdown = (item) => ({ ...item, breakdown: breakdownByTeamId.get(item.teamId) || [], drivers: (() => { if (!lastRaceId) return { driver1: null, driver2: null }; const drivers = fetchTeamDriversAtRace(lastRaceId, item.teamId); return { driver1: drivers[0] ?? null, driver2: drivers[1] ?? null }; })() }); if (!lastRaceId) return base.map(attachDriversAndBreakdown); const fetchTeamDriversAtRace = (raceId, teamId) => { const rows = queryDB(` SELECT DISTINCT rr.DriverID, bas.FirstName, bas.LastName, rr.FinishingPos FROM Races_Results rr JOIN Staff_BasicData bas ON bas.StaffID = rr.DriverID WHERE rr.RaceID = ? AND rr.TeamID = ? ORDER BY rr.FinishingPos ASC, rr.DriverID ASC `, [raceId, teamId], 'allRows') || []; return rows.slice(0, 2).map(r => { const driverID = r[0]; const driverName = formatNamesSimple([r[1], r[2]])[0]; const currentNumber = queryDB(` SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder = ? `, [driverID], 'singleValue'); return { id: Number(driverID), name: driverName, number: currentNumber != null ? Number(currentNumber) : null }; }); }; return base.map(attachDriversAndBreakdown); } export function fetchQualifyingStageCounts(year, formula = 1, isCurrentYear = true) { if (Number(formula) === 1 && !isCurrentYear) { const cutoffQ2 = getGlobals().isCreateATeam ? 16 : 15; const rows = queryDB(` SELECT ds.DriverID, bas.FirstName, bas.LastName, COALESCE(( SELECT rr.TeamID FROM Races_Results rr JOIN Races r ON r.RaceID = rr.RaceID WHERE rr.DriverID = ds.DriverID AND r.SeasonID = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID, SUM(CASE WHEN rr.StartingPos = 1 THEN 1 ELSE 0 END) AS PoleCount, SUM(CASE WHEN rr.StartingPos > 0 AND rr.StartingPos != 99 AND rr.StartingPos <= 10 THEN 1 ELSE 0 END) AS Q3Count, SUM(CASE WHEN rr.StartingPos > 0 AND rr.StartingPos != 99 AND rr.StartingPos <= ${cutoffQ2} THEN 1 ELSE 0 END) AS Q2Count FROM Races_DriverStandings ds JOIN Staff_BasicData bas ON bas.StaffID = ds.DriverID LEFT JOIN Races_Results rr ON rr.DriverID = ds.DriverID AND rr.Season = ? WHERE ds.SeasonID = ? AND ds.RaceFormula = ? GROUP BY ds.DriverID ORDER BY PoleCount DESC, Q3Count DESC, Q2Count DESC, ds.Position ASC `, [year, year, year, formula], 'allRows') || []; return rows.map(r => ({ id: r[0], name: formatNamesSimple([r[1], r[2]])[0], teamId: r[3], q2Count: r[6] ?? 0, q3Count: r[5] ?? 0, poleCount: r[4] ?? 0 })); } if (Number(formula) === 1) { const rows = queryDB(` SELECT ds.DriverID, bas.FirstName, bas.LastName, COALESCE(( SELECT rr.TeamID FROM Races_Results rr JOIN Races r ON r.RaceID = rr.RaceID WHERE rr.DriverID = ds.DriverID AND r.SeasonID = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 2 THEN q.RaceID END) AS Q2Count, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 3 THEN q.RaceID END) AS Q3Count, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 3 AND q.FinishingPos = 1 THEN q.RaceID END) AS PoleCount FROM Races_DriverStandings ds JOIN Staff_BasicData bas ON bas.StaffID = ds.DriverID LEFT JOIN Races_QualifyingResults q ON q.DriverID = ds.DriverID AND q.SeasonID = ds.SeasonID AND q.RaceFormula = ds.RaceFormula AND COALESCE(q.SprintShootout, 0) = 0 AND q.QualifyingStage IN (2, 3) WHERE ds.SeasonID = ? AND ds.RaceFormula = ? GROUP BY ds.DriverID ORDER BY Q3Count DESC, Q2Count DESC, ds.Position ASC `, [year, year, formula], 'allRows') || []; return rows.map(r => ({ id: r[0], name: formatNamesSimple([r[1], r[2]])[0], teamId: r[3], q2Count: r[4] ?? 0, q3Count: r[5] ?? 0, poleCount: r[6] ?? 0 })); } const rows = queryDB(` SELECT ds.DriverID, bas.FirstName, bas.LastName, COALESCE(( SELECT fr.TeamID FROM Races_FeatureRaceResults fr JOIN Races r ON r.RaceID = fr.RaceID WHERE fr.DriverID = ds.DriverID AND fr.SeasonID = ? AND fr.RaceFormula = ? ORDER BY r.Day DESC, r.RaceID DESC LIMIT 1 ), -1) AS TeamID, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 2 THEN q.RaceID END) AS Q2Count, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 3 THEN q.RaceID END) AS Q3Count, COUNT(DISTINCT CASE WHEN q.QualifyingStage = 3 AND q.FinishingPos = 1 THEN q.RaceID END) AS PoleCount FROM Races_DriverStandings ds JOIN Staff_BasicData bas ON bas.StaffID = ds.DriverID LEFT JOIN Races_QualifyingResults q ON q.DriverID = ds.DriverID AND q.SeasonID = ds.SeasonID AND q.RaceFormula = ds.RaceFormula AND q.QualifyingStage IN (2, 3) WHERE ds.SeasonID = ? AND ds.RaceFormula = ? GROUP BY ds.DriverID ORDER BY Q3Count DESC, Q2Count DESC, ds.Position ASC `, [year, formula, year, formula], 'allRows') || []; return rows.map(r => ({ id: r[0], name: formatNamesSimple([r[1], r[2]])[0], teamId: r[3], q2Count: r[4] ?? 0, q3Count: r[5] ?? 0, poleCount: r[6] ?? 0 })); } function pickValueFromType(item, type) { if (!item) return 0; switch (type) { case "wins": return item.totalWins ?? 0; case "podiums": return item.totalPodiums ?? 0; case "poles": return item.totalPoles ?? 0; case "champs": return item.totalChampionshipWins ?? 0; case "fastestLaps": return item.totalFastestLaps ?? 0; case "points": return item.totalPointsScored ?? 0; case "races": return item.totalStarts ?? 0; default: return 0; } } function mapExternalItem(item, type) { return { ...item, // forzados como pediste id: -1, retired: 1, teamId: -1, record: type, value: pickValueFromType(item, type), // por si en tu app esperas que existan siempre totalStarts: item.totalStarts ?? 0, totalPoles: item.totalPoles ?? 0, totalPodiums: item.totalPodiums ?? 0, totalWins: item.totalWins ?? 0, totalSprintWins: item.totalSprintWins ?? 0, totalChampionshipWins: item.totalChampionshipWins ?? 0, totalPointsScored: item.totalPointsScored ?? 0, totalFastestLaps: item.totalFastestLaps ?? 0, firstRace: item.firstRace ?? { season: 0, trackName: null }, firstPodium: item.firstPodium ?? { season: 0, trackName: null }, firstWin: item.firstWin ?? { season: 0, trackName: null }, lastWin: item.lastWin ?? { season: 0, trackName: null }, }; } function aggregateSeasonDriverRecords(records) { const byId = new Map(); (records || []).forEach((record) => { const driverId = Number(record?.id); const value = Number(record?.value) || 0; if (!driverId || value === 0) return; if (!byId.has(driverId)) { byId.set(driverId, { ...record, id: driverId, value }); return; } const current = byId.get(driverId); current.value += value; const teamId = Number(record?.teamId ?? -1); if (teamId !== -1) current.teamId = teamId; if (record?.name) current.name = record.name; if (record?.retired != null) current.retired = record.retired; }); return [...byId.values()].sort((a, b) => (Number(b?.value) || 0) - (Number(a?.value) || 0)); } function mergeWithExternalRecords(dbDrivers, externalJson, type, year) { const normName = (name) => { return (name || "").replace(/\s+/g, " ").trim().toLowerCase(); }; if (year !== "all") return dbDrivers || []; const byName = new Set((dbDrivers || []).map(d => normName(d.name))); const externalMapped = (externalJson || []) .map(it => mapExternalItem(it, type)) .filter(it => !byName.has(normName(it.name))); const merged = [...(dbDrivers || []), ...externalMapped]; // Ordenamos por la columna value desc merged.sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); return merged; } function fetchRacePointsSnapshot(raceIdNum) { const rows = queryDB( `SELECT DriverID, TeamID, Points FROM Races_Results WHERE RaceID = ?`, [raceIdNum], 'allRows' ) || []; const pointsByDriver = new Map(); const pointsByTeam = new Map(); for (const r of rows) { const driverId = Number(r?.[0]); const teamId = Number(r?.[1]); const pts = Number(r?.[2] ?? 0); pointsByDriver.set(driverId, pts); const prev = Number(pointsByTeam.get(teamId) ?? 0); pointsByTeam.set(teamId, prev + pts); } return { pointsByDriver, pointsByTeam }; } function fetchSprintPointsSnapshot(raceIdNum) { const rows = queryDB( `SELECT DriverID, TeamID, ChampionshipPoints FROM Races_SprintResults WHERE RaceID = ? AND RaceFormula = 1`, [raceIdNum], 'allRows' ) || []; const pointsByDriver = new Map(); const pointsByTeam = new Map(); for (const r of rows) { const driverId = Number(r?.[0]); const teamId = Number(r?.[1]); const pts = Number(r?.[2] ?? 0); pointsByDriver.set(driverId, pts); const prev = Number(pointsByTeam.get(teamId) ?? 0); pointsByTeam.set(teamId, prev + pts); } return { pointsByDriver, pointsByTeam }; } function fetchSprintPosToPointsMap(raceIdNum) { const rows = queryDB( `SELECT FinishingPos, ChampionshipPoints, DNF FROM Races_SprintResults WHERE RaceID = ? AND RaceFormula = 1`, [raceIdNum], 'allRows' ) || []; const posToPts = new Map(); for (const r of rows) { const pos = Number(r?.[0]); const pts = Number(r?.[1] ?? 0); const dnf = Number(r?.[2]) === 1; if (!dnf && pos > 0 && pos !== 99) posToPts.set(pos, Math.max(0, pts)); } return posToPts; } function fetchRaceDriverRecordsSnapshot(raceIdNum) { const rows = queryDB( `SELECT DriverID, FinishingPos, DNF, Points FROM Races_Results WHERE RaceID = ?`, [raceIdNum], 'allRows' ) || []; const byDriver = new Map(); for (const r of rows) { const driverId = Number(r?.[0]); const pos = Number(r?.[1]); const dnf = Number(r?.[2]) === 1; const pts = Number(r?.[3] ?? 0); const starts = 1; const wins = (!dnf && pos === 1) ? 1 : 0; const podiums = (!dnf && pos >= 1 && pos <= 3) ? 1 : 0; const points = Math.max(0, pts); byDriver.set(driverId, { starts, wins, podiums, points }); } return byDriver; } function diffPointsMaps(newMap, oldMap) { const deltas = new Map(); const keys = new Set([...(oldMap ? oldMap.keys() : []), ...(newMap ? newMap.keys() : [])]); for (const k of keys) { const before = Number(oldMap?.get(k) ?? 0); const after = Number(newMap?.get(k) ?? 0); const delta = after - before; if (delta !== 0) deltas.set(k, delta); } return deltas; } function diffDriverRecordMaps(newMap, oldMap) { const deltas = new Map(); const keys = new Set([...(oldMap ? oldMap.keys() : []), ...(newMap ? newMap.keys() : [])]); for (const k of keys) { const before = oldMap?.get(k) || { starts: 0, wins: 0, podiums: 0, points: 0 }; const after = newMap?.get(k) || { starts: 0, wins: 0, podiums: 0, points: 0 }; const dStarts = Number(after.starts ?? 0) - Number(before.starts ?? 0); const dWins = Number(after.wins ?? 0) - Number(before.wins ?? 0); const dPodiums = Number(after.podiums ?? 0) - Number(before.podiums ?? 0); const dPoints = Number(after.points ?? 0) - Number(before.points ?? 0); if (dStarts !== 0 || dWins !== 0 || dPodiums !== 0 || dPoints !== 0) { deltas.set(k, { starts: dStarts, wins: dWins, podiums: dPodiums, points: dPoints }); } } return deltas; } function resortF1StandingsForSeason(seasonId) { const seasonNum = Number(seasonId); const driverRows = queryDB( `SELECT DriverID, Points, Position FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = 1`, [seasonNum], 'allRows' ) || []; const existingDriverPos = new Map(); const pointsByDriver = new Map(); const driverIds = []; driverRows.forEach((r) => { const driverId = Number(r?.[0]); const pts = Number(r?.[1] ?? 0); const pos = Number(r?.[2]); driverIds.push(driverId); pointsByDriver.set(driverId, pts); existingDriverPos.set(driverId, pos); }); const finishCountsRows = queryDB( `SELECT DriverID, FinishingPos, COUNT(*) AS Cnt FROM Races_Results WHERE Season = ? AND FinishingPos > 0 AND FinishingPos != 99 GROUP BY DriverID, FinishingPos`, [seasonNum], 'allRows' ) || []; const countsByDriver = new Map(); finishCountsRows.forEach((r) => { const driverId = Number(r?.[0]); const finPos = Number(r?.[1]); const cnt = Number(r?.[2] ?? 0); if (!countsByDriver.has(driverId)) countsByDriver.set(driverId, new Map()); countsByDriver.get(driverId).set(finPos, cnt); }); const sortedDrivers = driverIds.slice().sort((a, b) => { const pa = Number(pointsByDriver.get(a) ?? 0); const pb = Number(pointsByDriver.get(b) ?? 0); if (pa !== pb) return pb - pa; const ca = countsByDriver.get(a) || new Map(); const cb = countsByDriver.get(b) || new Map(); for (let pos = 1; pos <= 30; pos++) { const da = Number(ca.get(pos) ?? 0); const db = Number(cb.get(pos) ?? 0); if (da !== db) return db - da; } const ea = Number(existingDriverPos.get(a) ?? Infinity); const eb = Number(existingDriverPos.get(b) ?? Infinity); if (ea !== eb) return ea - eb; return a - b; }); for (let i = 0; i < sortedDrivers.length; i++) { const driverId = sortedDrivers[i]; const pts = Number(pointsByDriver.get(driverId) ?? 0); queryDB( `UPDATE Races_DriverStandings SET Points = ?, Position = ?, LastPointsChange = 0, LastPositionChange = 0 WHERE SeasonID = ? AND RaceFormula = 1 AND DriverID = ?`, [pts, i + 1, seasonNum, driverId], 'run' ); } const teamRows = queryDB( `SELECT TeamID, Points, Position FROM Races_TeamStandings WHERE SeasonID = ? AND RaceFormula = 1`, [seasonNum], 'allRows' ) || []; const existingTeamPos = new Map(); const pointsByTeam = new Map(); const teamIds = []; teamRows.forEach((r) => { const teamId = Number(r?.[0]); const pts = Number(r?.[1] ?? 0); const pos = Number(r?.[2]); teamIds.push(teamId); pointsByTeam.set(teamId, pts); existingTeamPos.set(teamId, pos); }); const teamFinishCountsRows = queryDB( `SELECT TeamID, FinishingPos, COUNT(*) AS Cnt FROM Races_Results WHERE Season = ? AND FinishingPos > 0 AND FinishingPos != 99 GROUP BY TeamID, FinishingPos`, [seasonNum], 'allRows' ) || []; const countsByTeam = new Map(); teamFinishCountsRows.forEach((r) => { const teamId = Number(r?.[0]); const finPos = Number(r?.[1]); const cnt = Number(r?.[2] ?? 0); if (!countsByTeam.has(teamId)) countsByTeam.set(teamId, new Map()); countsByTeam.get(teamId).set(finPos, cnt); }); const sortedTeams = teamIds.slice().sort((a, b) => { const pa = Number(pointsByTeam.get(a) ?? 0); const pb = Number(pointsByTeam.get(b) ?? 0); if (pa !== pb) return pb - pa; const ca = countsByTeam.get(a) || new Map(); const cb = countsByTeam.get(b) || new Map(); for (let pos = 1; pos <= 30; pos++) { const da = Number(ca.get(pos) ?? 0); const db = Number(cb.get(pos) ?? 0); if (da !== db) return db - da; } const ea = Number(existingTeamPos.get(a) ?? Infinity); const eb = Number(existingTeamPos.get(b) ?? Infinity); if (ea !== eb) return ea - eb; return a - b; }); for (let i = 0; i < sortedTeams.length; i++) { const teamId = sortedTeams[i]; const pts = Number(pointsByTeam.get(teamId) ?? 0); queryDB( `UPDATE Races_TeamStandings SET Points = ?, Position = ?, LastPointsChange = 0, LastPositionChange = 0 WHERE SeasonID = ? AND RaceFormula = 1 AND TeamID = ?`, [pts, i + 1, seasonNum, teamId], 'run' ); } } export function editRaceResults(raceId, edits = [], opts = {}) { const raceIdNum = Number(raceId); if (!Array.isArray(edits) || edits.length === 0) return { ok: false, error: "No edits provided" }; const sessionKey = String(opts?.sessionKey || (opts?.isSprint ? "sprintrace" : "race")).toLowerCase(); const isSprint = sessionKey === "sprintrace" || sessionKey === "sprint" || sessionKey === "sprintrace"; try { queryDB(`BEGIN IMMEDIATE`, [], "run"); const seasonId = queryDB(`SELECT SeasonID FROM Races WHERE RaceID = ?`, [raceIdNum], "singleValue"); if (seasonId == null) { queryDB(`ROLLBACK`, [], "run"); return { ok: false, error: "Race not found" }; } const rowCount = Number(queryDB( isSprint ? `SELECT COUNT(*) FROM Races_SprintResults WHERE RaceID = ? AND RaceFormula = 1` : `SELECT COUNT(*) FROM Races_Results WHERE RaceID = ?`, [raceIdNum], "singleValue" ) ?? 0); if (rowCount > 0 && edits.length !== rowCount) { queryDB(`ROLLBACK`, [], "run"); return { ok: false, error: `Edits count (${edits.length}) does not match ${isSprint ? "sprint" : "race"} entries (${rowCount}).` }; } for (const edit of edits) { const dnf = Number(edit?.dnf); if (!(dnf === 0 || dnf === 1)) { queryDB(`ROLLBACK`, [], "run"); return { ok: false, error: "Invalid edits payload" }; } } const posSet = new Set(edits.map(e => Number(e?.finishingPos))); if (posSet.size !== edits.length) { queryDB(`ROLLBACK`, [], "run"); return { ok: false, error: "Duplicate finishing positions in edits." }; } const before = isSprint ? fetchSprintPointsSnapshot(raceIdNum) : fetchRacePointsSnapshot(raceIdNum); const beforeDriverRecords = isSprint ? null : fetchRaceDriverRecordsSnapshot(raceIdNum); if (!isSprint) { // Avoid UNIQUE constraint collisions while reordering (Season, RaceID, FinishingPos) queryDB( `UPDATE Races_Results SET FinishingPos = COALESCE(FinishingPos, 0) + 1000 WHERE RaceID = ?`, [raceIdNum], "run" ); for (const edit of edits) { const dnf = Number(edit.dnf) === 1 ? 1 : 0; const time = dnf ? 0 : Number(edit.time); queryDB( `UPDATE Races_Results SET FinishingPos = ?, Time = ?, DNF = ? WHERE RaceID = ? AND DriverID = ?`, [Number(edit.finishingPos), time, dnf, raceIdNum, Number(edit.driverId)], "run" ); } // Recalculate points for the edited race queryDB(`UPDATE Races_Results SET Points = 0 WHERE RaceID = ?`, [raceIdNum], "run"); const regs = fetchPointsRegulations(); const posPoints = new Map( (Array.isArray(regs?.positionAndPoints) ? regs.positionAndPoints : []) .map((r) => [Number(r?.[0]), Number(r?.[1])]) ); const lastRaceId = queryDB( `SELECT RaceID FROM Races WHERE SeasonID = ? ORDER BY Day DESC, RaceID DESC LIMIT 1`, [seasonId], "singleValue" ); const isLastRace = Number(lastRaceId) === raceIdNum; const doublePoints = isLastRace && Number(regs?.isLastraceDouble) === 1; const flBonusEnabled = Number(regs?.fastestLapBonusPoint) === 1; const rows = queryDB( `SELECT DriverID, FinishingPos, DNF, FastestLap FROM Races_Results WHERE RaceID = ? ORDER BY FinishingPos`, [raceIdNum], "allRows" ) || []; let fastestDriverId = null; let fastestLap = null; let fastestPos = null; for (const r of rows) { const fl = Number(r?.[3]); if (fl > 0 && (fastestLap == null || fl < fastestLap)) { fastestLap = fl; fastestDriverId = Number(r?.[0]); fastestPos = Number(r?.[1]); } } for (const r of rows) { const driverId = Number(r?.[0]); const pos = Number(r?.[1]); const dnf = Number(r?.[2]) === 1; let pts = 0; if (!dnf && pos >= 1 && pos <= 11) { pts = Number(posPoints.get(pos) ?? 0); } if (flBonusEnabled && fastestDriverId != null && driverId === fastestDriverId && Number(fastestPos) <= 10 && !dnf) { pts += 1; } if (doublePoints) pts *= 2; queryDB(`UPDATE Races_Results SET Points = ? WHERE RaceID = ? AND DriverID = ?`, [pts, raceIdNum, driverId], "run"); } } else { const posToPts = fetchSprintPosToPointsMap(raceIdNum); // Avoid UNIQUE constraint collisions while reordering queryDB( `UPDATE Races_SprintResults SET FinishingPos = COALESCE(FinishingPos, 0) + 1000 WHERE RaceID = ? AND RaceFormula = 1`, [raceIdNum], "run" ); for (const edit of edits) { const dnf = Number(edit.dnf) === 1 ? 1 : 0; const time = dnf ? 0 : Number(edit.time); queryDB( `UPDATE Races_SprintResults SET FinishingPos = ?, RaceTime = ?, DNF = ? WHERE RaceID = ? AND RaceFormula = 1 AND DriverID = ?`, [Number(edit.finishingPos), time, dnf, raceIdNum, Number(edit.driverId)], "run" ); } queryDB( `UPDATE Races_SprintResults SET ChampionshipPoints = 0 WHERE RaceID = ? AND RaceFormula = 1`, [raceIdNum], "run" ); const rows = queryDB( `SELECT DriverID, FinishingPos, DNF FROM Races_SprintResults WHERE RaceID = ? AND RaceFormula = 1 ORDER BY FinishingPos`, [raceIdNum], "allRows" ) || []; for (const r of rows) { const driverId = Number(r?.[0]); const pos = Number(r?.[1]); const dnf = Number(r?.[2]) === 1; const pts = (!dnf && pos > 0 && pos !== 99) ? Number(posToPts.get(pos) ?? 0) : 0; queryDB( `UPDATE Races_SprintResults SET ChampionshipPoints = ? WHERE RaceID = ? AND RaceFormula = 1 AND DriverID = ?`, [pts, raceIdNum, driverId], "run" ); } } const after = isSprint ? fetchSprintPointsSnapshot(raceIdNum) : fetchRacePointsSnapshot(raceIdNum); const afterDriverRecords = isSprint ? null : fetchRaceDriverRecordsSnapshot(raceIdNum); const driverDeltas = diffPointsMaps(after.pointsByDriver, before.pointsByDriver); for (const [driverId, delta] of driverDeltas.entries()) { queryDB( `UPDATE Races_DriverStandings SET Points = COALESCE(Points, 0) + ? WHERE SeasonID = ? AND RaceFormula = 1 AND DriverID = ?`, [Number(delta), Number(seasonId), Number(driverId)], "run" ); } const teamDeltas = diffPointsMaps(after.pointsByTeam, before.pointsByTeam); for (const [teamId, delta] of teamDeltas.entries()) { queryDB( `UPDATE Races_TeamStandings SET Points = COALESCE(Points, 0) + ? WHERE SeasonID = ? AND RaceFormula = 1 AND TeamID = ?`, [Number(delta), Number(seasonId), Number(teamId)], "run" ); } if (!isSprint && beforeDriverRecords && afterDriverRecords) { const recordDeltas = diffDriverRecordMaps(afterDriverRecords, beforeDriverRecords); for (const [driverId, delta] of recordDeltas.entries()) { queryDB( `UPDATE Staff_Driver_RaceRecordSinceGameStart SET TotalStarts = MAX(0, COALESCE(TotalStarts, 0) + ?), TotalWins = MAX(0, COALESCE(TotalWins, 0) + ?), TotalPodiums = MAX(0, COALESCE(TotalPodiums, 0) + ?), TotalPointsScored = MAX(0, COALESCE(TotalPointsScored, 0) + ?) WHERE StaffID = ? AND Formula = 1`, [Number(delta.starts), Number(delta.wins), Number(delta.podiums), Number(delta.points), Number(driverId)], "run" ); queryDB( `UPDATE Staff_Driver_RaceRecordPerSeason SET TotalStarts = MAX(0, COALESCE(TotalStarts, 0) + ?), TotalWins = MAX(0, COALESCE(TotalWins, 0) + ?), TotalPodiums = MAX(0, COALESCE(TotalPodiums, 0) + ?), TotalPointsScored = MAX(0, COALESCE(TotalPointsScored, 0) + ?) WHERE StaffID = ? AND SeasonID = ?`, [Number(delta.starts), Number(delta.wins), Number(delta.podiums), Number(delta.points), Number(driverId), Number(seasonId)], "run" ); } } resortF1StandingsForSeason(seasonId); queryDB(`COMMIT`, [], "run"); return { ok: true }; } catch (e) { try { queryDB(`ROLLBACK`, [], "run"); } catch (e2) { /* ignore */ } return { ok: false, error: e?.message || String(e) }; } } ================================================ FILE: src/js/backend/scriptUtils/regulationsUtils.js ================================================ import { queryDB } from "../dbManager.js"; export function fetchRegulationsData() { const enumRows = queryDB( `SELECT ChangeID, Name, CurrentValue, MinValue, MaxValue FROM Regulations_Enum_Changes`, [], "allRows" ) || []; const enumChanges = {}; for (const [ChangeID, Name, CurrentValue, MinValue, MaxValue] of enumRows) { enumChanges[Name] = { ChangeID, Name, CurrentValue, MinValue, MaxValue, }; } const pointRows = queryDB( `SELECT PointScheme, RacePos, Points FROM Regulations_NonTechnical_PointSchemes ORDER BY PointScheme ASC, RacePos ASC`, [], "allRows" ) || []; const pointSchemes = {}; for (const [PointScheme, RacePos, Points] of pointRows) { if (!pointSchemes[PointScheme]) pointSchemes[PointScheme] = []; pointSchemes[PointScheme].push({ RacePos, Points }); } const resourceRows = queryDB( `SELECT ResourcePackage, StandingPos, WindTunnelBlocks, CfdBlocks FROM Regulations_NonTechnical_PartResources ORDER BY ResourcePackage ASC, StandingPos ASC`, [], "allRows" ) || []; const partResources = {}; for (const [ResourcePackage, StandingPos, WindTunnelBlocks, CfdBlocks] of resourceRows) { if (!partResources[ResourcePackage]) partResources[ResourcePackage] = []; partResources[ResourcePackage].push({ StandingPos, WindTunnelBlocks, CfdBlocks, }); } return { enumChanges, pointSchemes, partResources }; } function isRowPresent(query, params) { const exists = queryDB(query, params, "singleValue"); return exists !== null && exists !== undefined; } export function updateOneRegulationEnumChange(name, currentValue, minValue, maxValue) { queryDB( `UPDATE Regulations_Enum_Changes SET CurrentValue = ?, MinValue = ?, MaxValue = ? WHERE Name = ?`, [currentValue, minValue, maxValue, name], "run" ); } export function updateRegulations(data) { const enumChanges = data?.enumChanges || {}; for (const [name, row] of Object.entries(enumChanges)) { if (!row || row.CurrentValue === undefined || row.CurrentValue === null) continue; queryDB( `UPDATE Regulations_Enum_Changes SET CurrentValue = ? WHERE Name = ?`, [row.CurrentValue, name], "run" ); } const pointSchemes = data?.pointSchemes || {}; for (const [schemeIdStr, rows] of Object.entries(pointSchemes)) { const schemeId = Number(schemeIdStr); if (!Array.isArray(rows)) continue; for (const r of rows) { const racePos = Number(r?.RacePos); const points = Number(r?.Points ?? 0); const present = isRowPresent( `SELECT 1 FROM Regulations_NonTechnical_PointSchemes WHERE PointScheme = ? AND RacePos = ? LIMIT 1`, [schemeId, racePos] ); if (present) { queryDB( `UPDATE Regulations_NonTechnical_PointSchemes SET Points = ? WHERE PointScheme = ? AND RacePos = ?`, [points, schemeId, racePos], "run" ); } else { queryDB( `INSERT INTO Regulations_NonTechnical_PointSchemes (PointScheme, RacePos, Points) VALUES (?, ?, ?)`, [schemeId, racePos, points], "run" ); } } } const partResources = data?.partResources || {}; for (const [packageIdStr, rows] of Object.entries(partResources)) { const packageId = Number(packageIdStr); if (!Array.isArray(rows)) continue; for (const r of rows) { const standingPos = Number(r?.StandingPos); const wind = Number(r?.WindTunnelBlocks ?? 0); const cfd = Number(r?.CfdBlocks ?? 0); const present = isRowPresent( `SELECT 1 FROM Regulations_NonTechnical_PartResources WHERE ResourcePackage = ? AND StandingPos = ? LIMIT 1`, [packageId, standingPos] ); if (present) { queryDB( `UPDATE Regulations_NonTechnical_PartResources SET WindTunnelBlocks = ?, CfdBlocks = ? WHERE ResourcePackage = ? AND StandingPos = ?`, [wind, cfd, packageId, standingPos], "run" ); } else { queryDB( `INSERT INTO Regulations_NonTechnical_PartResources (ResourcePackage, StandingPos, WindTunnelBlocks, CfdBlocks) VALUES (?, ?, ?, ?)`, [packageId, standingPos, wind, cfd], "run" ); } } } } ================================================ FILE: src/js/backend/scriptUtils/transferUtils.js ================================================ import { getGlobals } from "../commandGlobals"; import { queryDB } from "../dbManager"; export const minMaxTypeStaff = { driver: { salary: { 1: [14,30], 2: [7,12], 3: [0.5,6], 4: [0.2,1.2] }, starting_bonus: { 1: [2,4.5], 2: [1,2], 3: [0,1.6], 4: [0,0] }, year_end: { 1: [1,5], 2: [1,4], 3: [1,3], 4: [1,2] }, race_bonus: { 1: [1.5,2.5], 2: [0.9,1.7], 3: [0,0.7], 4: [0,0] }, race_bonus_pos: { 1: [1,3], 2: [2,5], 3: [7,10], 4: [9,10] } }, staff: { salary: { 1: [3.5,5], 2: [2.5,4], 3: [1.5,3], 4: [0.5,1.5] }, starting_bonus: { 1: [0.5,1.5], 2: [0.5,1], 3: [0,0.5], 4: [0,0.5] }, year_end: { 1: [1,5], 2: [1,4], 3: [1,3], 4: [1,2] } } }; export let CONTRACT_PLACEHOLDERS_24 = { teamID: 1, posInTeam: 1, startDay: 43831, endSeason: 2026, salary: 100000, startingBonus: 0, raceBonus: 0, raceBonusTargetPos: 1 }; function isBlankContractValue(value) { return value === null || value === undefined || (typeof value === "string" && value.trim() === ""); } function toContractInt(value, fallback) { if (isBlankContractValue(value)) return fallback; const parsed = Number.parseInt(String(value), 10); return Number.isFinite(parsed) ? parsed : fallback; } function sanitizeContractPayload(payload = {}) { console.log(getGlobals().currentDate); const seasonYear = getGlobals().currentDate[1]; console.log("Sanitizing contract payload:", payload); let staffID = toContractInt(payload.staffID, 0); if (staffID < 0) staffID = 0; let contractType = toContractInt(payload.contractType, 0); let teamID = toContractInt(payload.teamID, CONTRACT_PLACEHOLDERS_24.teamID); let posInTeam = toContractInt(payload.posInTeam, CONTRACT_PLACEHOLDERS_24.posInTeam); let startDay = toContractInt(payload.startDay, CONTRACT_PLACEHOLDERS_24.startDay); let endSeason = toContractInt(payload.endSeason, seasonYear + 1); let salary = toContractInt(payload.salary, CONTRACT_PLACEHOLDERS_24.salary); let startingBonus = toContractInt(payload.startingBonus, CONTRACT_PLACEHOLDERS_24.startingBonus); let raceBonus = toContractInt(payload.raceBonus, CONTRACT_PLACEHOLDERS_24.raceBonus); let raceBonusTargetPos = toContractInt(payload.raceBonusTargetPos, CONTRACT_PLACEHOLDERS_24.raceBonusTargetPos); return { staffID, contractType, teamID, posInTeam, startDay, endSeason, salary, startingBonus, raceBonus, raceBonusTargetPos }; } function getCurrentMainContractSnapshot(driverID) { const safeDriverID = toContractInt(driverID, 0); const row = queryDB( `SELECT TeamID, PosInTeam FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 ORDER BY TeamID ASC LIMIT 1`, [safeDriverID], "singleRow" ) || []; return sanitizeContractPayload({ staffID: safeDriverID, contractType: 0, teamID: row[0], posInTeam: row[1] }); } export function checkAndFixContract(driverID, teamID) { const safeDriverID = toContractInt(driverID, 0); const safeTeamID = toContractInt(teamID, CONTRACT_PLACEHOLDERS_24.teamID); const row = queryDB( `SELECT StaffID, ContractType, TeamID, PosInTeam, StartDay, EndSeason, Salary, StartingBonus, RaceBonus, RaceBonusTargetPos FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`, [safeDriverID, safeTeamID], "singleRow" ); console.log("Checking contract for DriverID:", safeDriverID, "TeamID:", safeTeamID, "Result:", row); if (!row) return null; const current = { staffID: row[0], contractType: row[1], teamID: row[2], posInTeam: row[3], startDay: row[4], endSeason: row[5], salary: row[6], startingBonus: row[7], raceBonus: row[8], raceBonusTargetPos: row[9] }; const sanitized = sanitizeContractPayload(current); console.log("Sanitized contract values:", sanitized); const changed = ( current.teamID !== sanitized.teamID || current.posInTeam !== sanitized.posInTeam || current.startDay !== sanitized.startDay || current.endSeason !== sanitized.endSeason || current.salary !== sanitized.salary || current.startingBonus !== sanitized.startingBonus || current.raceBonus !== sanitized.raceBonus || current.raceBonusTargetPos !== sanitized.raceBonusTargetPos ); console.log("Contract values changed:", changed); if (changed) { queryDB( `UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ?, StartDay = ?, EndSeason = ?, Salary = ?, StartingBonus = ?, RaceBonus = ?, RaceBonusTargetPos = ? WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`, [ sanitized.teamID, sanitized.posInTeam, sanitized.startDay, sanitized.endSeason, sanitized.salary, sanitized.startingBonus, sanitized.raceBonus, sanitized.raceBonusTargetPos, safeDriverID, safeTeamID ], "run" ); } return sanitized; } export function transferJuniorDriver(driverID,newTeamID, posInTeam, yearIteration = "24") { const teamHasDriverInPosition = queryDB(`SELECT StaffID FROM Staff_Contracts WHERE TeamID = ? AND PosInTeam = ? AND ContractType = 0`,[newTeamID,posInTeam],"singleValue"); if (teamHasDriverInPosition) { //remove that driver from the team queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`,[teamHasDriverInPosition,newTeamID],'run'); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`,[teamHasDriverInPosition],'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = NULL WHERE StaffID = ?`,[teamHasDriverInPosition],'run'); } //check if the driver has a contract with another team that is in between 11 and 31 (both included) const hasJunioorContract = queryDB(`SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID > 10 AND TeamID < 32`,[driverID],"singleValue"); if (hasJunioorContract) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND TeamID = ? AND ContractType = 0`,[driverID,hasJunioorContract],'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = NULL WHERE StaffID = ?`,[driverID],'run'); } //add the driver to the new team const day = queryDB("SELECT Day FROM Player_State",[],"singleValue"); const year = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const yearEnd = (parseInt(year) + 1).toString(); const salary = "100000"; const contractValues = sanitizeContractPayload({ staffID: driverID, contractType: 0, teamID: newTeamID, posInTeam, startDay: day, endSeason: yearEnd, salary, startingBonus: 0, raceBonus: 0, raceBonusTargetPos: 1 }); if (yearIteration === "23") { queryDB(`INSERT INTO Staff_Contracts VALUES (?, 0, 1, ?, 1, ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', 0, 1, '[OPINION_STRING_NEUTRAL]')`, [contractValues.staffID,contractValues.startDay,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } else if (yearIteration === "24") { queryDB( `INSERT INTO Staff_Contracts VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ?, 0.5, 0)`, [contractValues.staffID,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[contractValues.posInTeam,driverID],'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = ? WHERE StaffID = ?`,[contractValues.posInTeam,driverID],'run'); rearrangeDriverEngineerPairings(contractValues.teamID); //if teamid is less than 22, then its 2, if more is 3 const juniorFormula = (contractValues.teamID < 22) ? 2 : 3; //check if he is in the standings of that formula let positionInStandings = queryDB(`SELECT Position FROM Races_DriverStandings WHERE SeasonID = ? AND DriverID = ? AND RaceFormula = ?`,[year,driverID,juniorFormula],"singleValue"); if (!positionInStandings) { //insert in position maximum possible const actualMaxPosition = queryDB(`SELECT COUNT(*) FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = ?`,[year,juniorFormula],"singleValue"); positionInStandings = actualMaxPosition + 1; queryDB(`INSERT INTO Races_DriverStandings VALUES (?, ?, 0, ?, 0, 0, ?)`,[year,driverID,positionInStandings,juniorFormula],'run'); } } export function hireDriver(type,driverID,teamID,position,salary = "",startingBonus = "",raceBonus = "",raceBonusPos = "",yearEnd = "",yearIteration = "24") { if (type === "auto" || salary === "" || startingBonus === "") { const params = getParamsAutoContract(driverID,teamID,position,yearIteration); salary = params.salary; yearEnd = params.yearEnd; startingBonus = params.startingBonus; raceBonus = params.raceBonus; raceBonusPos = params.raceBonusPos; } const day = queryDB("SELECT Day FROM Player_State",[],"singleValue"); const year = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const staffType = fetchTypeStaff(driverID); const contractValues = sanitizeContractPayload({ staffID: driverID, contractType: 0, teamID, posInTeam: position, startDay: day, endSeason: yearEnd, salary, startingBonus, raceBonus, raceBonusTargetPos: raceBonusPos }); const isRetired = queryDB(`SELECT Retired FROM Staff_GameData WHERE StaffID = ?`,[driverID],"singleValue"); if (isRetired === 1) { queryDB(`UPDATE Staff_GameData SET Retired = 0 WHERE StaffID = ?`,[driverID],'run'); } if (yearIteration === "23") { queryDB( `INSERT INTO Staff_Contracts VALUES (?, 0, 1, ?, 1, ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', 0, 1, '[OPINION_STRING_NEUTRAL]')`, [contractValues.staffID,contractValues.startDay,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } else if (yearIteration === "24") { queryDB( `INSERT INTO Staff_Contracts VALUES (?, 0, ?, ?, ?, ?, ?, ?, ?, ?, 0.5, 0)`, [contractValues.staffID,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } if (contractValues.posInTeam < 3 && staffType === 0) { queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[contractValues.posInTeam,driverID],'run'); const isDrivingInF2F3 = queryDB( `SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND (TeamID > 10 AND TeamID < 32)`, [driverID], "singleValue" ); if (isDrivingInF2F3 !== null && isDrivingInF2F3 !== undefined) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`,[driverID,isDrivingInF2F3],'run'); queryDB(`UPDATE Staff_DriverData SET FeederSeriesAssignedCarNumber = NULL WHERE StaffID = ?`,[driverID],'run'); } let positionInStandings = queryDB( `SELECT MAX(Position) FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = 1`, [year], "singleValue" ); let pointsDriverInStandings = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 1`, [driverID,year], "singleValue" ); if (pointsDriverInStandings === null || pointsDriverInStandings === undefined) { pointsDriverInStandings = 0; queryDB( `INSERT INTO Races_DriverStandings VALUES (?, ?, ?, ?, 0, 0, 1)`, [year,driverID,pointsDriverInStandings,positionInStandings + 1],'run' ); } const wasInF2 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`, [driverID,year], "singleValue" ); const wasInF3 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`, [driverID,year], "singleValue" ); if (wasInF2 !== null && wasInF2 !== undefined) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`,[driverID,year],'run'); } if (wasInF3 !== null && wasInF3 !== undefined) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`,[driverID,year],'run'); } const driverHasNumber = queryDB( `SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder = ?`, [driverID], "singleValue" ); if (!driverHasNumber) { freeNumbersNotF1(); const freeNumbers = queryDB("SELECT Number FROM Staff_DriverNumbers WHERE CurrentHolder IS NULL AND Number != 0",[],"allRows"); if (freeNumbers && freeNumbers.length > 0) { const randIndex = Math.floor(Math.random() * freeNumbers.length); const newNum = freeNumbers[randIndex][0]; queryDB(`UPDATE Staff_DriverNumbers SET CurrentHolder = ? WHERE Number = ?`,[driverID,newNum],'run'); } } } rearrangeDriverEngineerPairings(contractValues.teamID); fixDriverStandings(); } export function freeNumbersNotF1() { const numbers = queryDB("SELECT CurrentHolder, Number FROM Staff_DriverNumbers WHERE Number != 0 AND CurrentHolder IS NOT NULL",[],"allRows"); if (numbers) { numbers.forEach(row => { const driver = row[0]; const number = row[1]; const teamId = queryDB( `SELECT MIN(TeamID) FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0`, [driver], "singleValue" ); if (teamId !== null && teamId > 10 && teamId < 32) { queryDB(`UPDATE Staff_DriverNumbers SET CurrentHolder = NULL WHERE Number = ?`,[number],'run'); } }); } } export function fetchTypeStaff(driverID) { return queryDB(`SELECT StaffType FROM Staff_GameData WHERE StaffID = ?`,[driverID],"singleValue"); } export function getParamsAutoContract(driverID,teamID,position,yearIteration = "24") { const day = queryDB("SELECT Day FROM Player_State",[],"singleValue"); const year = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const [tier,type,rating] = getTier(driverID); // Calcular salary const salaryRange = minMaxTypeStaff[type].salary[tier]; let salary = (Math.round((Math.random() * (salaryRange[1] - salaryRange[0]) + salaryRange[0]) * 1000) / 1000) * 1000000; salary = salary.toString(); const startingBonusRange = minMaxTypeStaff[type].starting_bonus[tier]; let startingBonus = (Math.round((Math.random() * (startingBonusRange[1] - startingBonusRange[0]) + startingBonusRange[0]) * 1000) / 1000) * 1000000; startingBonus = startingBonus.toString(); let yearEnd = parseInt(year) + Math.floor(Math.random() * (minMaxTypeStaff[type].year_end[tier][1] - minMaxTypeStaff[type].year_end[tier][0] + 1)) + minMaxTypeStaff[type].year_end[tier][0]; yearEnd = yearEnd.toString(); let raceBonus = "0"; let hasBonus = false; if (type === "driver") { if (tier === 1) { const rbRange = minMaxTypeStaff[type].race_bonus[tier]; raceBonus = (Math.round((Math.random() * (rbRange[1] - rbRange[0]) + rbRange[0]) * 1000) / 1000) * 1000000; raceBonus = raceBonus.toString(); hasBonus = true; } else if (tier === 2) { if (Math.floor(Math.random() * 11) <= 7) { const rbRange = minMaxTypeStaff[type].race_bonus[tier]; raceBonus = (Math.round((Math.random() * (rbRange[1] - rbRange[0]) + rbRange[0]) * 1000) / 1000) * 1000000; raceBonus = raceBonus.toString(); hasBonus = true; } else { raceBonus = "0"; hasBonus = false; } } else if (tier === 3) { if (Math.floor(Math.random() * 11) <= 2) { const rbRange = minMaxTypeStaff[type].race_bonus[tier]; raceBonus = (Math.round((Math.random() * (rbRange[1] - rbRange[0]) + rbRange[0]) * 1000) / 1000) * 1000000; raceBonus = raceBonus.toString(); hasBonus = true; } else { raceBonus = "0"; hasBonus = false; } } else if (tier === 4) { raceBonus = "0"; hasBonus = false; } } else { raceBonus = "0"; hasBonus = false; } const driverBirthDate = queryDB(`SELECT DOB_ISO FROM Staff_BasicData WHERE StaffID = ?`,[driverID],"singleValue"); if (driverBirthDate) { const yob = parseInt(driverBirthDate.split("-")[0]); if ((parseInt(year) - yob > 34) && type === "driver") { yearEnd = (parseInt(year) + Math.floor(Math.random() * 2) + 1).toString(); } } let raceBonusPos = "1"; if (hasBonus) { let prestigeTableName = "Board_Prestige"; if (yearIteration === "24") { prestigeTableName = "Board_TeamRating"; } const prestigeValues = queryDB( `SELECT PtsFromConstructorResults, PtsFromDriverResults, PtsFromSeasonsEntered, PtsFromChampionshipsWon FROM ${prestigeTableName} WHERE SeasonID = ? AND TeamID = ?`, [year,teamID], "allRows" ); let prestige = 0; if (prestigeValues) { prestigeValues.forEach(row => { prestige += row[0]; }); } if (prestige >= 750) { raceBonusPos = (Math.floor(Math.random() * (3 - 1 + 1)) + 1).toString(); } else if (prestige >= 600) { raceBonusPos = (Math.floor(Math.random() * (5 - 2 + 1)) + 2).toString(); } else if (prestige >= 525) { raceBonusPos = (Math.floor(Math.random() * (10 - 7 + 1)) + 7).toString(); } else if (prestige >= 450) { raceBonusPos = (Math.floor(Math.random() * (10 - 9 + 1)) + 9).toString(); } else { raceBonus = "0"; raceBonusPos = "1"; } } return { salary,yearEnd,position,startingBonus,raceBonus,raceBonusPos }; } export function fireDriver(driverID,teamID) { const position = queryDB(`SELECT PosInTeam FROM Staff_Contracts WHERE StaffID = ?`,[driverID],"singleValue"); queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`,[driverID,teamID],'run'); if (position < 3) { queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`,[driverID],'run'); } const engineerID = queryDB( `SELECT RaceEngineerID FROM Staff_RaceEngineerDriverAssignments WHERE IsCurrentAssignment = 1 AND DriverID = ?`, [driverID], "singleValue" ); if (engineerID) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ? AND DriverID = ?`,[engineerID,driverID],'run'); } } export function removeFutureContract(driverID) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 3`,[driverID],'run'); } export function rearrangeDriverEngineerPairings(teamID) { const engineers = queryDB( `SELECT gam.StaffID FROM Staff_GameData gam JOIN Staff_Contracts con ON gam.StaffID = con.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND gam.StaffType = 2`, [teamID], "allRows" ); const drivers = queryDB( `SELECT gam.StaffID FROM Staff_GameData gam JOIN Staff_Contracts con ON gam.StaffID = con.StaffID WHERE con.TeamID = ? AND con.ContractType = 0 AND gam.StaffType = 0 AND PosInTeam <= 2`, [teamID], "allRows" ); if (drivers && drivers.length === 2 && engineers && engineers.length === 2) { drivers.forEach(driverRow => { const driverID = driverRow[0]; queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE DriverID = ?`,[driverID],'run'); }); engineers.forEach(engineerRow => { const engineerID = engineerRow[0]; queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 0 WHERE RaceEngineerID = ?`,[engineerID],'run'); }); const pair1Exists = queryDB( `SELECT DaysTogether FROM Staff_RaceEngineerDriverAssignments WHERE DriverID = ? AND RaceEngineerID = ?`, [drivers[0][0],engineers[0][0]], "singleValue" ); if (pair1Exists !== null && pair1Exists !== undefined) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 1 WHERE DriverID = ? AND RaceEngineerID = ?`,[drivers[0][0],engineers[0][0]],'run'); } else { queryDB(`INSERT INTO Staff_RaceEngineerDriverAssignments VALUES (?, ?, 0, 0, 1)`,[engineers[0][0],drivers[0][0]],'run'); } const pair2Exists = queryDB( `SELECT DaysTogether FROM Staff_RaceEngineerDriverAssignments WHERE DriverID = ? AND RaceEngineerID = ?`, [drivers[1][0],engineers[1][0]], "singleValue" ); if (pair2Exists !== null && pair2Exists !== undefined) { queryDB(`UPDATE Staff_RaceEngineerDriverAssignments SET IsCurrentAssignment = 1 WHERE DriverID = ? AND RaceEngineerID = ?`,[drivers[1][0],engineers[1][0]],'run'); } else { queryDB(`INSERT INTO Staff_RaceEngineerDriverAssignments VALUES (?, ?, 0, 0, 1)`,[engineers[1][0],drivers[1][0]],'run'); } } } export function swapDrivers(driver1ID,driver2ID) { const contract1 = getCurrentMainContractSnapshot(driver1ID); const contract2 = getCurrentMainContractSnapshot(driver2ID); const safeDriver1ID = contract1.staffID; const safeDriver2ID = contract2.staffID; const team1ID = contract1.teamID; const team2ID = contract2.teamID; const position1 = contract1.posInTeam; const position2 = contract2.posInTeam; const year = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const type1 = fetchTypeStaff(safeDriver1ID); const type2 = fetchTypeStaff(safeDriver2ID); const isStaff = (type1 === 1 || type2 === 1); if (position1 < 3 && position2 < 3 && !isStaff) { queryDB(`UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ?`,[team2ID,position2,safeDriver1ID],'run'); queryDB(`UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ?`,[team1ID,position1,safeDriver2ID],'run'); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[position2,safeDriver1ID],'run'); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[position1,safeDriver2ID],'run'); rearrangeDriverEngineerPairings(team1ID); rearrangeDriverEngineerPairings(team2ID); } else if ((position1 >= 3 && position2 >= 3) || isStaff) { queryDB(`UPDATE Staff_Contracts SET TeamID = ? WHERE ContractType = 0 AND StaffID = ?`,[team2ID,safeDriver1ID],'run'); queryDB(`UPDATE Staff_Contracts SET TeamID = ? WHERE ContractType = 0 AND StaffID = ?`,[team1ID,safeDriver2ID],'run'); } else if (position1 >= 3) { const isDrivingInF2 = queryDB( `SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND (TeamID > 10 AND TeamID < 32)`, [safeDriver1ID], "singleValue" ); if (isDrivingInF2) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`,[safeDriver1ID,isDrivingInF2],'run'); } const type = fetchTypeStaff(safeDriver1ID); if (parseInt(type) === 0) { const wasInF2 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`, [safeDriver1ID,year], "singleValue" ); const wasInF3 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`, [safeDriver1ID,year], "singleValue" ); if (wasInF2) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`,[safeDriver1ID,year],'run'); } if (wasInF3) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`,[safeDriver1ID,year],'run'); } const position1InStandings = queryDB( `SELECT MAX(Position) FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ?`, [year], "singleValue" ); let pointsDriver1InStandings = queryDB( `SELECT Points FROM Races_DriverStandings WHERE RaceFormula = 1 AND DriverID = ? AND SeasonID = ?`, [safeDriver1ID,year], "singleValue" ); if (pointsDriver1InStandings === null || pointsDriver1InStandings === undefined) { pointsDriver1InStandings = 0; queryDB( `INSERT INTO Races_DriverStandings VALUES (?, ?, ?, ?, 0, 0, 1)`, [year,safeDriver1ID,pointsDriver1InStandings,position1InStandings + 1],'run' ); } } queryDB( `UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ? AND TeamID = ?`, [team1ID,position1,safeDriver2ID,team2ID],'run' ); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`,[safeDriver2ID],'run'); queryDB( `UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ? AND TeamID = ?`, [team2ID,position2,safeDriver1ID,team1ID],'run' ); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[position2,safeDriver1ID],'run'); rearrangeDriverEngineerPairings(team1ID); rearrangeDriverEngineerPairings(team2ID); } else if (position2 >= 3) { const isDrivingInF2 = queryDB( `SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND (TeamID > 10 AND TeamID < 32)`, [safeDriver2ID], "singleValue" ); if (isDrivingInF2) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0 AND TeamID = ?`,[safeDriver2ID,isDrivingInF2],'run'); } const type = fetchTypeStaff(safeDriver1ID); if (parseInt(type) === 0) { const wasInF2 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`, [safeDriver2ID,year], "singleValue" ); const wasInF3 = queryDB( `SELECT Points FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`, [safeDriver2ID,year], "singleValue" ); if (wasInF2) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 2`,[safeDriver2ID,year],'run'); } if (wasInF3) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 3`,[safeDriver2ID,year],'run'); } const position2InStandings = queryDB( `SELECT MAX(Position) FROM Races_DriverStandings WHERE RaceFormula = 1 AND SeasonID = ?`, [year], "singleValue" ); let pointsDriver2InStandings = queryDB( `SELECT Points FROM Races_DriverStandings WHERE RaceFormula = 1 AND DriverID = ? AND SeasonID = ?`, [safeDriver2ID,year], "singleValue" ); if (pointsDriver2InStandings === null || pointsDriver2InStandings === undefined) { pointsDriver2InStandings = 0; queryDB( `INSERT INTO Races_DriverStandings VALUES (?, ?, ?, ?, 0, 0, 1)`, [year,safeDriver2ID,pointsDriver2InStandings,position2InStandings + 1],'run' ); } } queryDB( `UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ? AND TeamID = ?`, [team2ID,position2,safeDriver1ID,team1ID],'run' ); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = NULL WHERE StaffID = ?`,[safeDriver1ID],'run'); queryDB( `UPDATE Staff_Contracts SET TeamID = ?, PosInTeam = ? WHERE ContractType = 0 AND StaffID = ? AND TeamID = ?`, [team1ID,position1,safeDriver2ID,team2ID],'run' ); queryDB(`UPDATE Staff_DriverData SET AssignedCarNumber = ? WHERE StaffID = ?`,[position1,safeDriver2ID],'run'); rearrangeDriverEngineerPairings(team1ID); rearrangeDriverEngineerPairings(team2ID); } checkAndFixContract(safeDriver1ID, team2ID); checkAndFixContract(safeDriver2ID, team1ID); fixDriverStandings(); } export function editContract(driverID,salary,endSeason,startingBonus,raceBonus,raceBonusTargetPos) { let safeDriverID = toContractInt(driverID, 0); const contractValues = sanitizeContractPayload({ staffID: safeDriverID, contractType: 0, salary, endSeason, startingBonus, raceBonus, raceBonusTargetPos }); const hasContract = queryDB( `SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 0`, [safeDriverID], "singleValue" ); if (hasContract !== null && hasContract !== undefined) { queryDB( `UPDATE Staff_Contracts SET Salary = ?, EndSeason = ?, StartingBonus = ?, RaceBonus = ?, RaceBonusTargetPos = ? WHERE ContractType = 0 AND StaffID = ?`, [contractValues.salary,contractValues.endSeason,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos,safeDriverID],'run' ); } } export function futureContract(teamID,driverID,salary,endSeason,startingBonus,raceBonus,raceBonusTargetPos,position,yearIteration = "24") { let safeDriverID = toContractInt(driverID, 0); const requestedTeamID = toContractInt(teamID, CONTRACT_PLACEHOLDERS_24.teamID); if (requestedTeamID === -1) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 3`,[safeDriverID],'run'); } else { const season = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const day = getExcelDate(parseInt(season) + 1); const contractValues = sanitizeContractPayload({ staffID: safeDriverID, contractType: 3, teamID: requestedTeamID, posInTeam: position, startDay: day, endSeason, salary, startingBonus, raceBonus, raceBonusTargetPos }); let alreadyHasFutureContract = queryDB( `SELECT TeamID FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 3`, [safeDriverID], "singleValue" ); alreadyHasFutureContract = toContractInt(alreadyHasFutureContract, -1); if (alreadyHasFutureContract !== contractValues.teamID) { queryDB(`DELETE FROM Staff_Contracts WHERE StaffID = ? AND ContractType = 3`,[safeDriverID],'run'); if (yearIteration === "24") { queryDB( `INSERT INTO Staff_Contracts VALUES (?, 3, ?, ?, ?, ?, ?, ?, ?, ?, 0.5, 0)`, [contractValues.staffID,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } else if (yearIteration === "23") { queryDB( `INSERT INTO Staff_Contracts VALUES (?, 3, 1, ?, 1, ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', ?, 1, '[OPINION_STRING_NEUTRAL]', 0, 1, '[OPINION_STRING_NEUTRAL]')`, [contractValues.staffID,contractValues.startDay,contractValues.teamID,contractValues.posInTeam,contractValues.startDay,contractValues.endSeason,contractValues.salary,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos],'run' ); } } else { queryDB( `UPDATE Staff_Contracts SET PosInTeam = ?, Salary = ?, EndSeason = ?, StartingBonus = ?, RaceBonus = ?, RaceBonusTargetPos = ? WHERE StaffID = ? AND TeamID = ? AND ContractType = 3`, [contractValues.posInTeam,contractValues.salary,contractValues.endSeason,contractValues.startingBonus,contractValues.raceBonus,contractValues.raceBonusTargetPos,contractValues.staffID,alreadyHasFutureContract],'run' ); } } } export function getExcelDate(year) { const excelStartDate = new Date(1900,0,1); const targetDate = new Date(year,0,1); const diffDays = Math.floor((targetDate - excelStartDate) / (1000 * 60 * 60 * 24)) + 2; return diffDays; } export function unretire(driverID) { queryDB(`UPDATE Staff_GameData SET Retired = 0 WHERE StaffID = ?`,[driverID],'run'); queryDB(`UPDATE Staff_DriverData SET HasSuperLicense = 1 WHERE StaffID = ?`,[driverID],'run'); } export function getTier(driverID) { const driverStats = queryDB(`SELECT Val FROM Staff_PerformanceStats WHERE StaffID = ?`,[driverID],"allRows"); let type = "driver"; let rating = 0; if (driverStats && driverStats.length === 9) { const cornering = parseFloat(driverStats[0][0]); const braking = parseFloat(driverStats[1][0]); const control = parseFloat(driverStats[2][0]); const smoothness = parseFloat(driverStats[3][0]); const adaptability = parseFloat(driverStats[4][0]); const overtaking = parseFloat(driverStats[5][0]); const defence = parseFloat(driverStats[6][0]); const reactions = parseFloat(driverStats[7][0]); const accuracy = parseFloat(driverStats[8][0]); rating = (cornering + braking * 0.75 + reactions * 0.5 + control * 0.75 + smoothness * 0.5 + accuracy * 0.75 + adaptability * 0.25 + overtaking * 0.25 + defence * 0.25) / 5; rating = Math.round(rating); } else if (driverStats && driverStats.length > 0) { type = "staff"; driverStats.forEach(stat => { rating += parseFloat(stat[0]); }); rating = rating / driverStats.length; } else { rating = 0; } let tier = 4; if (rating >= 89) { tier = 1; } else if (rating >= 85) { tier = 2; } else if (rating >= 80) { tier = 3; } else { tier = 4; } return [tier,type,rating]; } export function getDriverOverall(driverID) { const driverStats = queryDB(`SELECT Val FROM Staff_PerformanceStats WHERE StaffID = ?`,[driverID],"allRows"); let rating = 0; if (driverStats && driverStats.length === 9) { const cornering = parseFloat(driverStats[0][0]); const braking = parseFloat(driverStats[1][0]); const control = parseFloat(driverStats[2][0]); const smoothness = parseFloat(driverStats[3][0]); const adaptability = parseFloat(driverStats[4][0]); const overtaking = parseFloat(driverStats[5][0]); const defence = parseFloat(driverStats[6][0]); const reactions = parseFloat(driverStats[7][0]); const accuracy = parseFloat(driverStats[8][0]); rating = (cornering + braking * 0.75 + reactions * 0.5 + control * 0.75 + smoothness * 0.5 + accuracy * 0.75 + adaptability * 0.25 + overtaking * 0.25 + defence * 0.25) / 5; } else if (driverStats && driverStats.length > 0) { driverStats.forEach(stat => { rating += parseFloat(stat[0]); }); rating = rating / driverStats.length; } else { rating = 0; } return Math.round(rating); } export function getDriverId(name) { let driver = name.charAt(0).toUpperCase() + name.slice(1); const multipleDrivers = ["Perez","Raikkonen","Hulkenberg","Toth","Stanek","Villagomez","Bolukbasi","Marti"]; if (multipleDrivers.includes(driver)) { driver = driver + "1"; } let driverId; if (driver === "Aleclerc") { driverId = 132; } else if (driver === "Devries") { driverId = 76; } else if (driver === "Dschumacher") { driverId = 270; } else { const lastName = `[StaffName_Surname_${driver}]`; driverId = queryDB(`SELECT StaffID FROM Staff_BasicData WHERE LastName = ?`,[lastName],"singleValue"); } return driverId; } export function fixDriverStandings() { const year = queryDB("SELECT CurrentSeason FROM Player_State",[],"singleValue"); const driversInStandings = queryDB(`SELECT DriverID FROM Races_DriverStandings WHERE SeasonID = ? AND RaceFormula = 1`,[year],"allRows"); if (driversInStandings) { driversInStandings.forEach(driverRow => { const driverID = driverRow[0]; const isDriver = queryDB(`SELECT StaffType FROM Staff_GameData WHERE StaffID = ?`,[driverID],"singleValue"); if (isDriver !== 0) { queryDB(`DELETE FROM Races_DriverStandings WHERE DriverID = ? AND SeasonID = ? AND RaceFormula = 1`,[driverID,year],'run'); } }); } } ================================================ FILE: src/js/backend/scriptUtils/triggerUtils.js ================================================ import { queryDB } from "../dbManager"; const difficultyDict = { 0: { name: "default", perc: 0, "7and8": 0, "9": 0, reduction: 0, research: 0 }, 1: { name: "extraHard", perc: 0.5, "7and8": 0.01, "9": 0.005, reduction: 0, research: 8 }, 2: { name: "brutal", perc: 0.8, "7and8": 0.016, "9": 0.008, reduction: 0.05, research: 14 }, 3: { name: "unfair", perc: 1.5, "7and8": 0.03, "9": 0.015, reduction: 0.11, research: 30 }, 4: { name: "insane", perc: 2, "7and8": 0.04, "9": 0.02, reduction: 0.16, research: 45 }, 5: { name: "impossible", perc: 3, "7and8": 0.06, "9": 0.03, reduction: 0.2, research: 65 } }; const invertedDifficultyDict = Object.fromEntries( Object.entries(difficultyDict).map(([key, entry]) => [entry.name, Number(key)]) ); export function manageDifficultyTriggers(triggerList) { console.log("Managing difficulty triggers with list:", triggerList); if (triggerList.statDif !== undefined) manageDesignBoostTriggers(triggerList.statDif); if (triggerList.designTimeDif !== undefined) manageDesignTimeTriggers(triggerList.designTimeDif); if (triggerList.lightDif !== undefined) manageWeightTrigger(triggerList.lightDif); if (triggerList.buildDif !== undefined) manageInstantBuildTriggers(triggerList.buildDif); if (triggerList.researchDif !== undefined) manageResearchTriggers(triggerList.researchDif); } export function manageWeightTrigger(triggerLevel) { console.log("Managing weight trigger with level:", triggerLevel); queryDB("DROP TRIGGER IF EXISTS reduced_weight_reducedWeight", [], 'run'); queryDB("DROP TRIGGER IF EXISTS reduced_weight_normal", [], 'run'); queryDB("DROP TRIGGER IF EXISTS reduced_weight_extraHard", [], 'run'); queryDB("DROP TRIGGER IF EXISTS reduced_weight_extreme", [], 'run'); queryDB("DROP TRIGGER IF EXISTS reduced_weight_impossible", [], 'run'); triggerLevel = parseInt(triggerLevel); let triggerSQL = ""; if (triggerLevel > 0) { if (triggerLevel === 1) { triggerSQL = ` CREATE TRIGGER reduced_weight_extraHard AFTER INSERT ON Parts_Designs_StatValues FOR EACH ROW WHEN ( SELECT TeamID FROM Parts_Designs WHERE DesignID = NEW.DesignID ) != (SELECT TeamID FROM Player) AND NEW.PartStat = 15 BEGIN UPDATE Parts_Designs_StatValues SET Value = 200, unitValue = ( SELECT CASE PD.PartType WHEN 3 THEN 4340 WHEN 4 THEN 1800 WHEN 5 THEN 2240 WHEN 6 THEN 3300 WHEN 7 THEN 2680 WHEN 8 THEN 2180 ELSE value END FROM Parts_Designs PD WHERE PD.DesignID = NEW.DesignID ) WHERE DesignID = NEW.DesignID AND PartStat = 15; END; `; } else if (triggerLevel === 5) { triggerSQL = ` CREATE TRIGGER reduced_weight_impossible AFTER INSERT ON Parts_Designs_StatValues FOR EACH ROW WHEN ( SELECT TeamID FROM Parts_Designs WHERE DesignID = NEW.DesignID ) != (SELECT TeamID FROM Player) AND NEW.PartStat = 15 BEGIN UPDATE Parts_Designs_StatValues SET Value = 0, unitValue = ( SELECT CASE PD.PartType WHEN 3 THEN 3800 WHEN 4 THEN 1250 WHEN 5 THEN 1650 WHEN 6 THEN 2750 WHEN 7 THEN 2100 WHEN 8 THEN 1700 ELSE value END FROM Parts_Designs PD WHERE PD.DesignID = NEW.DesignID ) WHERE DesignID = NEW.DesignID AND PartStat = 15; END; `; } if (triggerSQL) queryDB(triggerSQL, [], 'run'); } } export function manageDesignTimeTriggers(triggerLevel) { queryDB("DROP TRIGGER IF EXISTS designTime_extraHard", [], 'run'); queryDB("DROP TRIGGER IF EXISTS designTime_brutal", [], 'run'); queryDB("DROP TRIGGER IF EXISTS designTime_unfair", [], 'run'); queryDB("DROP TRIGGER IF EXISTS designTime_insane", [], 'run'); queryDB("DROP TRIGGER IF EXISTS designTime_impossible", [], 'run'); } export function manageDesignBoostTriggers(triggerLevel) { triggerLevel = parseInt(triggerLevel); queryDB("DROP TRIGGER IF EXISTS difficulty_reducedWeight", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_extraHard", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_reducedWeight", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_brutal", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_unfair", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_insane", [], 'run'); queryDB("DROP TRIGGER IF EXISTS difficulty_impossible", [], 'run'); let triggerSQL = ""; if (triggerLevel > 0) { const triggerName = `difficulty_${difficultyDict[triggerLevel].name}`; const increase_perc = difficultyDict[triggerLevel].perc; const increase_7and8 = difficultyDict[triggerLevel]["7and8"]; const increase_9 = difficultyDict[triggerLevel]["9"]; triggerSQL = ` CREATE TRIGGER ${triggerName} AFTER INSERT ON Parts_Designs_StatValues FOR EACH ROW WHEN ( SELECT TeamID FROM Parts_Designs WHERE DesignID = NEW.DesignID AND ValidFrom = (SELECT CurrentSeason FROM Player_State) ) != (SELECT TeamID FROM Player) AND NEW.PartStat != 15 BEGIN UPDATE Parts_Designs_StatValues SET unitValue = CASE WHEN NEW.PartStat IN (7, 8) THEN unitValue + ${increase_7and8} WHEN NEW.PartStat = 9 THEN unitValue + ${increase_9} ELSE unitValue + ${increase_perc} END, Value = CASE WHEN NEW.PartStat IN (0, 1, 2, 3, 4, 5) THEN (unitValue + ${increase_perc}) * 10 WHEN NEW.PartStat = 6 THEN ((unitValue + ${increase_perc}) - 90) * 1000 / 10 WHEN NEW.PartStat = 7 THEN (unitValue + ${increase_7and8} - 3) / 0.002 WHEN NEW.PartStat = 8 THEN (unitValue + ${increase_7and8} - 5) / 0.002 WHEN NEW.PartStat = 9 THEN (unitValue + ${increase_9} - 7) / 0.001 WHEN NEW.PartStat = 10 THEN ((unitValue + ${increase_perc}) - 90) * 1000 / 10 WHEN NEW.PartStat = 11 THEN (85 - (unitValue + ${increase_perc})) * 1000 / 20 WHEN NEW.PartStat = 12 THEN ((unitValue + ${increase_perc}) - 70) * 1000 / 15 WHEN NEW.PartStat = 13 THEN (unitValue + ${increase_perc}) * 10 WHEN NEW.PartStat = 14 THEN (85 - (unitValue + ${increase_perc})) * 1000 / 15 WHEN NEW.PartStat = 15 THEN ((unitValue + ${increase_perc}) - 40) * 1000 / 30 WHEN NEW.PartStat = 18 THEN ((unitValue + ${increase_perc}) - 40) * 1000 / 30 WHEN NEW.PartStat = 19 THEN ((unitValue + ${increase_perc}) - 40) * 1000 / 30 ELSE NULL END WHERE DesignID = NEW.DesignID AND PartStat = NEW.PartStat AND PartStat != 15; UPDATE Parts_TeamExpertise SET Expertise = Expertise * ( (SELECT Value FROM Parts_Designs_StatValues WHERE DesignID = NEW.DesignID AND PartStat = NEW.PartStat) / COALESCE( (SELECT Value FROM Parts_Designs_StatValues WHERE PartStat = NEW.PartStat AND DesignID = ( SELECT MAX(DesignID) FROM Parts_Designs WHERE DesignID < NEW.DesignID AND PartType = (SELECT PartType FROM Parts_Designs WHERE DesignID = NEW.DesignID) AND TeamID = (SELECT TeamID FROM Parts_Designs WHERE DesignID = NEW.DesignID) ) ), (SELECT Value FROM Parts_Designs_StatValues WHERE DesignID = NEW.DesignID AND PartStat = NEW.PartStat ) ) ) WHERE TeamID = (SELECT TeamID FROM Parts_Designs WHERE DesignID = NEW.DesignID) AND PartType = (SELECT PartType FROM Parts_Designs WHERE DesignID = NEW.DesignID) AND PartStat = NEW.PartStat; END; `; queryDB(triggerSQL, [], 'run'); } } export function manageInstantBuildTriggers(triggerLevel) { queryDB("DROP TRIGGER IF EXISTS instant_build_insane", [], 'run'); queryDB("DROP TRIGGER IF EXISTS instant_build_impossible", [], 'run'); } export function manageResearchTriggers(triggerLevel) { queryDB("DROP TRIGGER IF EXISTS research_reducedWeight", [], 'run'); queryDB("DROP TRIGGER IF EXISTS research_extraHard", [], 'run'); queryDB("DROP TRIGGER IF EXISTS research_brutal", [], 'run'); queryDB("DROP TRIGGER IF EXISTS research_unfair", [], 'run'); queryDB("DROP TRIGGER IF EXISTS research_insane", [], 'run'); queryDB("DROP TRIGGER IF EXISTS research_impossible", [], 'run'); triggerLevel = parseInt(triggerLevel); let triggerSQL = ""; if (triggerLevel > 0) { const triggerName = `research_${difficultyDict[triggerLevel].name}`; const researchExp = difficultyDict[triggerLevel].research; triggerSQL = ` CREATE TRIGGER ${triggerName} AFTER UPDATE ON Parts_Designs FOR EACH ROW WHEN NEW.DesignWork >= NEW.DesignWorkMax AND NEW.TeamID != (SELECT TeamID FROM Player) AND NEW.ValidFrom = (SELECT CurrentSeason FROM Player_State) + 1 BEGIN UPDATE Parts_Designs_StatValues SET ExpertiseGain = ExpertiseGain + ${researchExp} WHERE DesignID = NEW.DesignID; UPDATE Parts_TeamExpertise SET NextSeasonExpertise = NextSeasonExpertise + ${researchExp / 2} WHERE TeamID = NEW.TeamID AND PartType = NEW.PartType; END; `; queryDB(triggerSQL, [], 'run'); } } export function upgradeFactories(triggerLevel) { if (triggerLevel === 4) { queryDB("UPDATE Buildings_HQ SET BuildingID = 34, DegradationValue = 1 WHERE BuildingType = 3 AND TeamID != (SELECT TeamID FROM Player) AND BuildingID < 34", [], 'run'); } else if (triggerLevel === 6) { queryDB("UPDATE Buildings_HQ SET BuildingID = 35, DegradationValue = 1 WHERE BuildingType = 3 AND TeamID != (SELECT TeamID FROM Player) AND BuildingID < 35", [], 'run'); } else if (triggerLevel === -1) { queryDB("UPDATE Buildings_HQ SET BuildingID = 33, DegradationValue = 1 WHERE BuildingType = 3 AND TeamID != (SELECT TeamID FROM Player) AND BuildingID >= 34", [], 'run'); } } export function manageRefurbishTrigger(type) { queryDB("DROP TRIGGER IF EXISTS refurbish_fix", [], 'run'); if (type === 1) { const triggerSQL = ` CREATE TRIGGER refurbish_fix AFTER UPDATE ON Buildings_HQ FOR EACH ROW BEGIN UPDATE Buildings_HQ SET DegradationValue = 1 WHERE DegradationValue < 0.7 AND TeamID != (SELECT TeamID FROM Player); END; `; queryDB(triggerSQL, [], 'run'); } } export function fetchExistingTriggers() { let highest_difficulty = 0; const triggerList = { lightDif: -1, researchDif: -1, buildDif: -1, statDif: -1, designTimeDif: -1 }; let refurbish = 0; let frozenMentality = 0; let freezeDevelopment = 0; const triggers = queryDB("SELECT name FROM sqlite_master WHERE type='trigger';", [], "allRows"); if (triggers && triggers.length) { triggers.forEach(row => { const triggerName = row[0]; if (triggerName === "freeze_development") { freezeDevelopment = 1; } const parts = triggerName.split("_"); const dif = parts[parts.length - 1]; console.log("Processing trigger:", triggerName, "with difficulty part:", dif); const dif_level = invertedDifficultyDict[dif] !== undefined ? invertedDifficultyDict[dif] : 0; console.log("Mapped difficulty level:", dif_level); const type_trigger = parts[0]; if (type_trigger === "difficulty") { triggerList.statDif = dif_level; } else if (type_trigger === "designTime") { triggerList.designTimeDif = dif_level; } else if (type_trigger === "instant") { triggerList.buildDif = dif_level; } else if (type_trigger === "research") { triggerList.researchDif = dif_level; } else if (type_trigger === "reduced") { console.log("Found weight trigger with difficulty level:", dif_level); if (dif_level === 6) { triggerList.lightDif = 2; } else{ triggerList.lightDif = dif_level; } } else if (type_trigger === "refurbish") { refurbish = 1; } else if (type_trigger === "clear") { frozenMentality = 1; } if (dif_level > highest_difficulty) highest_difficulty = dif_level; }); } return { highest_difficulty, triggerList, refurbish, frozenMentality, freezeDevelopment }; } export function deleteProblematicTriggers() { const triggerStartNames = ["trg_injury_revert"]; for (const startName of triggerStartNames) { const triggers = queryDB("SELECT name FROM sqlite_master WHERE type='trigger' AND name LIKE ?;", [`${startName}%`], "allRows") || []; triggers.forEach(row => { const triggerName = row?.[0]; if (!triggerName) return; const escaped = String(triggerName).replace(/"/g, '""'); queryDB(`DROP TRIGGER IF EXISTS "${escaped}"`, [], 'run'); }); } queryDB("DROP TABLE IF EXISTS Custom_Injury_Swaps;", [], 'run'); } export function editFreezeMentality(state) { if (state === 0) { queryDB("DROP TRIGGER IF EXISTS update_Opinion_After_Insert;", [], 'run'); queryDB("DROP TRIGGER IF EXISTS update_Opinion_After_Update;", [], 'run'); queryDB("DROP TRIGGER IF EXISTS clear_Staff_Mentality_Statuses;", [], 'run'); queryDB("DROP TRIGGER IF EXISTS clear_Staff_Mentality_AreaOpinions;", [], 'run'); queryDB("DROP TRIGGER IF EXISTS clear_Staff_Mentality_Events;", [], 'run'); queryDB("DROP TRIGGER IF EXISTS reset_Staff_State;", [], 'run'); } else { queryDB(` CREATE TRIGGER IF NOT EXISTS update_Opinion_After_Insert AFTER INSERT ON Staff_Mentality_AreaOpinions BEGIN UPDATE Staff_Mentality_AreaOpinions SET Opinion = 2 WHERE Opinion != 2; END; `, [], 'run'); queryDB(` CREATE TRIGGER IF NOT EXISTS update_Opinion_After_Update AFTER UPDATE OF Opinion ON Staff_Mentality_AreaOpinions BEGIN UPDATE Staff_Mentality_AreaOpinions SET Opinion = 2 WHERE Opinion != 2; END; `, [], 'run'); queryDB(` CREATE TRIGGER IF NOT EXISTS clear_Staff_Mentality_Statuses AFTER INSERT ON Staff_Mentality_Statuses BEGIN DELETE FROM Staff_Mentality_Statuses; END; `, [], 'run'); queryDB(` CREATE TRIGGER IF NOT EXISTS clear_Staff_Mentality_Events AFTER INSERT ON Staff_Mentality_Events BEGIN DELETE FROM Staff_Mentality_Events; END; `, [], 'run'); queryDB(` CREATE TRIGGER IF NOT EXISTS reset_Staff_State AFTER UPDATE ON Staff_State BEGIN UPDATE Staff_State SET Mentality = 50, MentalityOpinion = 2; END; `, [], 'run'); } } export function editFreezeDevelopment(state) { queryDB("DROP TRIGGER IF EXISTS freeze_development", [], 'run'); if (parseInt(state) === 1) { queryDB(` CREATE TRIGGER freeze_development AFTER UPDATE ON Parts_Designs FOR EACH ROW WHEN NEW.TeamID != (SELECT TeamID FROM Player) AND NEW.PartType BETWEEN 3 AND 8 AND ( NEW.DesignWork != OLD.DesignWork OR NEW.DayCompleted != OLD.DayCompleted OR NEW.ValidFrom != OLD.ValidFrom OR NEW.DayCreated != OLD.DayCreated ) BEGIN UPDATE Parts_Designs SET DesignWork = OLD.DesignWork, DayCompleted = OLD.DayCompleted, ValidFrom = OLD.ValidFrom, DayCreated = OLD.DayCreated WHERE rowid = NEW.rowid; END; `, [], 'run'); } } ================================================ FILE: src/js/backend/worker.js ================================================ import { fetchSeasonResults, fetchEventsFrom, fetchTeamsStandings, fetchTeamsStandingsWithPositionChange, fetchDrivers, fetchStaff, fetchEngines, fetchYear, fetchDriverNumbers, checkCustomTables, checkYearSave, fetchOneDriverSeasonResults, fetchOneTeamSeasonResults, fetchEventsDoneFrom, updateCustomEngines, fetchDriversPerYear, fetchDriverContracts, fetchJuniorTeamDriverNames, editEngines, updateCustomConfig, fetchCustomConfig, fetch2025ModData, fetch2026ModData, check2025ModCompatibility, fetchPointsRegulations, fetchSessionResults, getDate, setCustomSaveConfig, check2026ModCompatibility, snapshotEnginePowerProgression } from "./scriptUtils/dbUtils"; import { getPerformanceAllTeamsSeason, getAttributesAllTeams, getPerformanceAllCars, getAttributesAllCars, getAduoEngineUpgradeRaceIds } from "./scriptUtils/carAnalysisUtils" import { setDatabase, getMetadata, getDatabase } from "./dbManager"; import { fetchHead2Head, fetchHead2HeadTeam } from "./scriptUtils/head2head"; import { editTeam, fetchTeamData } from "./scriptUtils/editTeamUtils"; import { overwritePerformanceTeam, updateItemsForDesignDict, fitLoadoutsDict, getPartsFromTeam, getUnitValueFromParts, getAllPartsFromTeam, getMaxDesign, getUnitValueFromOnePart, deleteCustomEngineAndReassign, getTeamExpertise, updateTeamExpertise } from "./scriptUtils/carAnalysisUtils"; import { setGlobals, getGlobals } from "./commandGlobals"; import { editAge, editMarketability, editName, editRetirement, editSuperlicense, editCode, editMentality, editStats, setAllDriversStatsTo85 } from "./scriptUtils/eidtStatsUtils"; import { editCalendar, fetchCalendar } from "./scriptUtils/calendarUtils"; import { fireDriver, hireDriver, swapDrivers, editContract, futureContract, transferJuniorDriver, CONTRACT_PLACEHOLDERS_24 } from "./scriptUtils/transferUtils"; import { change2024Standings, changeDriverLineUps, changeStats, removeFastestLap, timeTravelWithData, manageAffiliates, changeRaces, manageStandings, insertStaff2025, manageFeederSeries, changeDriverEngineerPairs, updatePerofmrnace2025, fixes_mod, change2025Standings, updateCalendar2026, changeStats2026, insertStaff2026, changeLineUps2026, changeDriverNumbers2026, apply2026EnginePerformanceChanges, updatePerofmrnace2026, changeAdditionalRegulations2026, fixesMod2026} from "./scriptUtils/modUtils"; import { generate_news, getOneQualiDetails, getOneRaceDetails, getTransferDetails, getTeamComparisonDetails, getFullChampionSeasonDetails, generateTurningResponse, upsertNews, updateNewsFields, upsertTurningPoints, loadTPFromDB, getCurrentAndNextSeasonGridLineups, computeStableKey, migrateLegacyData, loadNewsMapFromDB, ensureTurningPointsStructure, deleteNews, deleteTurningPoints, getNewsAndTpYearsAvailable, getNewsFromSeason, deleteNewByKey, checkDoublePointsBug, fixDoublePointsBug, getFullFeederSeriesDetails, getCustomNewsOptions, getRaceDriversForCustomNews, createCustomNewsEntry } from "./scriptUtils/newsUtils"; import { fetchSeasonReviewData, getSelectedRecord, getSelectedTeamRecord, editRaceResults } from "./scriptUtils/recordUtils"; import { teamReplaceDict } from "./commandGlobals"; import { excelToDate } from "./scriptUtils/eidtStatsUtils"; import { analyzeFileToDatabase, repack } from "./UESaveHandler"; import { fetchRegulationsData, updateRegulations } from "./scriptUtils/regulationsUtils.js"; import { deleteProblematicTriggers } from "./scriptUtils/triggerUtils.js"; import { fetchCountryLocaleForCode, fetchRandomDraftForename, fetchRandomStaffDraft } from "./scriptUtils/createStaffUtils.js"; import initSqlJs from 'sql.js'; import { combined_dict } from "../frontend/config"; // Diccionario de comandos const workerCommands = { loadDB: async (data, postMessage) => { console.log(data) const SQL = await initSqlJs({ locateFile: file => 'https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.13.0/sql-wasm.wasm', wasmMemory: new WebAssembly.Memory({ initial: 1024, maximum: 2048 }) }); const { db, metadata } = await analyzeFileToDatabase(data.file, SQL); console.log(metadata) let day = metadata.careerSaveMetadata.Day; let date = excelToDate(day); setDatabase(db, metadata); postMessage({ responseMessage: "Database loaded", content: date }); }, exportSave: async (data, postMessage) => { const db = getDatabase(); const metadata = getMetadata(); const result = repack(db, metadata); postMessage({ responseMessage: "Database exported", content: result }); }, panicDownload: async (data, postMessage) => { deleteProblematicTriggers(); const db = getDatabase(); const metadata = getMetadata(); const result = repack(db, metadata); postMessage({ responseMessage: "Database exported", content: result }); }, yearSelected: (data, postMessage) => { const year = data.year const isCurrentYear = data.isCurrentYear ?? true; const formula = data.formula ? Number(data.formula) : 1; const results = fetchSeasonResults(year, isCurrentYear, formula === 1, formula); const events = fetchEventsFrom(year, formula); const teams = fetchTeamsStandingsWithPositionChange(year, formula); const pointsInfo = fetchPointsRegulations() postMessage({ responseMessage: "Results fetched", content: [events, results, teams, pointsInfo] }); }, saveSelected: (data, postMessage) => { const yearData = checkYearSave(); postMessage({ responseMessage: "Game Year", content: yearData }); checkCustomTables(yearData[0]); if (yearData[1] !== null) { setGlobals({ createTeam: true }); } else { setGlobals({ createTeam: false }); } setGlobals({ year: yearData[0] }); const date = getDate(); setGlobals({ date: date }); CONTRACT_PLACEHOLDERS_24.endSeason = Number(yearData[0]) + 1; const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Save loaded succesfully", content: drivers, noti_msg: "Save loaded succesfully" }); const staff = fetchStaff(yearData[0]); postMessage({ responseMessage: "Staff fetched", content: staff }); const customConfig = fetchCustomConfig(); postMessage({ responseMessage: "Config", content: customConfig }); const engines = fetchEngines(); postMessage({ responseMessage: "Engines fetched", content: engines }); const calendar = fetchCalendar(); postMessage({ responseMessage: "Calendar fetched", content: calendar }); const regulations = fetchRegulationsData(); postMessage({ responseMessage: "Regulations fetched", content: regulations }); const year = fetchYear(); postMessage({ responseMessage: "Year fetched", content: year }); const previousYear = Number(year) - 1; const standings = fetchTeamsStandings(previousYear, 1); postMessage({responseMessage: "Previous year teams standings fetched", content: { year: previousYear, standings }}); const numbers = fetchDriverNumbers(); postMessage({ responseMessage: "Numbers fetched", content: numbers }); const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const aduoEngineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); postMessage({ responseMessage: "Season performance fetched", content: [performance, races, aduoEngineUpgradeRaceIds] }); const attributes = getAttributesAllTeams(yearData[2]); postMessage({ responseMessage: "Performance fetched", content: [performance[performance.length - 1], attributes] }); const carPerformance = getPerformanceAllCars(yearData[2]); const carAttributes = getAttributesAllCars(yearData[2]); postMessage({ responseMessage: "Cars fetched", content: [carPerformance, carAttributes] }); const mod2026Data = fetch2026ModData(); const mod2025Data = fetch2025ModData(); postMessage({ responseMessage: "Mod data fetched", content: { ...mod2025Data, ...mod2026Data } }); const mod25Compatibility = check2025ModCompatibility(yearData[0]); postMessage({ responseMessage: "Mod compatibility", content: mod25Compatibility }); const mod2026Compatibility = check2026ModCompatibility(yearData[0]); postMessage({ responseMessage: "Mod 2026 compatibility", content: mod2026Compatibility }); const wasError2025 = fixes_mod(); if (wasError2025) { postMessage({ responseMessage: "Mod fixes", content: "", noti_msg: "An error in the 2025 DLC has been automatically fixed", unlocksDownload: true }); } const wasError2026 = fixesMod2026(); console.log("Was error 2026:", wasError2026); if (wasError2026.generalWasError) { postMessage({ responseMessage: "Mod fixes", content: "", noti_msg: "An error in the 2026 DLC has been automatically fixed", unlocksDownload: true }); } fetchSeasonResults(year, true, true, 1); postMessage({ responseMessage: "Save selected finished" }); }, configuredH2H: (data, postMessage) => { if (data.h2h !== "-1") { let h2hRes; if (data.mode === "driver") { h2hRes = fetchHead2Head(data.h2h[0], data.h2h[1], data.year, data.isCurrentYear); } else if (data.mode === "team") { h2hRes = fetchHead2HeadTeam(data.h2h[0], data.h2h[1], data.year, data.isCurrentYear); } if (h2hRes) { postMessage({ responseMessage: "H2H fetched", content: h2hRes, isEditCommand: true }); } } const h2hDrivers = []; data.graph.forEach(driver => { let res; if (data.mode === "driver") { res = fetchOneDriverSeasonResults(driver, data.year, data.isCurrentYear); } else if (data.mode === "team") { res = fetchOneTeamSeasonResults(driver, data.year); } h2hDrivers.push(res); }); // Consulta eventos y los envía al frontend const eventsDone = fetchEventsDoneFrom(data.year); const allEvents = fetchEventsFrom(data.year); h2hDrivers.push(eventsDone); h2hDrivers.unshift(allEvents); postMessage({ responseMessage: "H2HDriver fetched", content: h2hDrivers }); }, customEngines: (data, postMessage) => { snapshotEnginePowerProgression(Object.keys(data?.enginesData || {}), 'pre_engine_edit'); updateCustomEngines(data.enginesData); postMessage({ responseMessage: "Custom engines updated", noti_msg: "Succesfully updated the custom engines", isEditCommand: true, unlocksDownload: true }); const engines = fetchEngines(); postMessage({ responseMessage: "Engines fetched", content: engines }); const yearData = checkYearSave(); const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const engineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); postMessage({ responseMessage: "Season performance fetched", content: [performance, races, engineUpgradeRaceIds] }); const attributes = getAttributesAllTeams(yearData[2]); postMessage({ responseMessage: "Performance fetched", content: [performance[performance.length - 1], attributes] }); }, deleteCustomEngine: (data, postMessage) => { const res = deleteCustomEngineAndReassign(data?.engineId, data?.fallbackEngineId); if (!res.ok) { postMessage({ responseMessage: "Error", error: res.error || "Failed to delete custom engine" }); return; } postMessage({ responseMessage: "Custom engine deleted", noti_msg: "Custom engine deleted", isEditCommand: true, unlocksDownload: true }); const engines = fetchEngines(); postMessage({ responseMessage: "Engines fetched", content: engines }); }, yearSelectedH2H: (data, postMessage) => { const drivers = fetchDriversPerYear(data.year); postMessage({ responseMessage: "DriversH2H fetched", content: drivers }); }, teamRequest: (data, postMessage) => { const teamID = data.teamID; const teamData = fetchTeamData(teamID); postMessage({ responseMessage: "TeamData fetched", content: teamData }); }, performanceRequest: (data, postMessage) => { const globals = getGlobals(); const designDict = getPartsFromTeam(data.teamID); const unitValues = getUnitValueFromParts(designDict); const allParts = getAllPartsFromTeam(data.teamID); const maxDesign = getMaxDesign(); const expertise = getTeamExpertise(data.teamID, globals.yearIteration); const designResponse = { responseMessage: "Parts stats fetched", content: [unitValues, allParts, maxDesign, expertise] }; postMessage(designResponse); }, editExpertise: (data, postMessage) => { const globals = getGlobals(); updateTeamExpertise(data.teamID, data.expertise, globals.yearIteration); postMessage({ responseMessage: "Expertise updated", noti_msg: `Succesfully edited ${teamReplaceDict[data.teamName]}'s expertise`, isEditCommand: true, unlocksDownload: true }); const expertise = getTeamExpertise(data.teamID, globals.yearIteration); postMessage({ responseMessage: "Team expertise fetched", content: expertise }); }, driverRequest: (data, postMessage) => { const contract = fetchDriverContracts(data.driverID); postMessage({ responseMessage: "Contract fetched", content: contract }); }, juniorTeamDriversRequest: (data, postMessage) => { const teamID = Number(data.teamID); if (teamID < 11 || teamID > 31) { postMessage({ responseMessage: "Error", error: "Invalid junior team id" }); return; } const driverNames = fetchJuniorTeamDriverNames(teamID); postMessage({ responseMessage: "Junior team drivers fetched", content: { teamID, driverNames } }); }, partRequest: (data, postMessage) => { const partValues = getUnitValueFromOnePart(data.designID); postMessage({ responseMessage: "Part values fetched", content: partValues }); }, editTeam: (data, postMessage) => { editTeam(data); postMessage({ responseMessage: "Team updated", noti_msg: `Succesfully edited ${teamReplaceDict[data.teamName]}'s details`, isEditCommand: true, unlocksDownload: true }); }, editStats: (data, postMessage) => { const globals = getGlobals(); editRetirement(data.driverID, data.isRetired); if (data.typeStaff === "0") { editSuperlicense(data.driverID, data.superLicense); if (globals.yearIteration === "24") { editMarketability(data.driverID, data.marketability); } } editStats(data.driverID, data.typeStaff, data.statsArray, data.retirement, data.driverNum, data.wants1); if (data.mentality !== "-1" && globals.yearIteration == "24") { editMentality(data.driverID, data.mentality); } editAge(data.driverID, data.age); if (data.newName !== "-1") { editName(data.driverID, data.newName); } if (data.newCode !== "-1") { editCode(data.driverID, data.newCode); } postMessage({ responseMessage: "Stats updated", noti_msg: `Succesfully edited ${data.driver}'s stats`, isEditCommand: true, unlocksDownload: true }); }, fetchRandomStaffDraft: (data, postMessage) => { const yearData = checkYearSave(); const draft = fetchRandomStaffDraft(data.typeStaff, yearData[0]); postMessage({ responseMessage: "Random staff draft fetched", content: draft }); }, fetchRandomDraftForename: (data, postMessage) => { const res = fetchRandomDraftForename(data.gender, data.staffNameLocale); postMessage({ responseMessage: "Random draft forename fetched", content: { ...res, draftId: data.draftId, gender: data.gender } }); }, fetchCountryLocaleForCode: (data, postMessage) => { const res = fetchCountryLocaleForCode(data.code); postMessage({ responseMessage: "Draft country locale fetched", content: { ...res, draftId: data.draftId } }); }, devSetAllDriversStats85: (data, postMessage) => { setAllDriversStatsTo85(); postMessage({ responseMessage: "Dev: driver stats updated", noti_msg: "All drivers stats set to 85", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); const staff = fetchStaff(yearData[0]); postMessage({ responseMessage: "Staff fetched", content: staff }); }, devDownloadDatabase: (data, postMessage) => { const db = getDatabase(); const metadata = getMetadata(); if (!db || !metadata) { throw new Error("No database loaded"); } postMessage({ responseMessage: "Dev database downloaded", content: { filename: metadata.filename + ".db", fileData: db.export() } }); }, editPerformance: (data, postMessage) => { let globals = getGlobals(); const yearData = checkYearSave(); overwritePerformanceTeam(data.teamID, data.parts, globals.isCreateATeam, globals.yearIteration, data.loadouts); updateItemsForDesignDict(data.n_parts_designs, data.teamID) fitLoadoutsDict(data.loadouts, data.teamID) const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const aduoEngineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); const performanceResponse = { responseMessage: "Season performance fetched", content: [performance, races, aduoEngineUpgradeRaceIds], noti_msg: `Succesfully edited ${teamReplaceDict[data.teamName]}'s car performance` }; postMessage(performanceResponse); const attibutes = getAttributesAllTeams(yearData[2]); const attributesResponse = { responseMessage: "Performance fetched", content: [performance[performance.length - 1], attibutes] }; postMessage(attributesResponse); const carPerformance = getPerformanceAllCars(yearData[2]); const carAttributes = getAttributesAllCars(yearData[2]); const carPerformanceResponse = { responseMessage: "Cars fetched", content: [carPerformance, carAttributes], isEditCommand: true, unlocksDownload: true }; postMessage(carPerformanceResponse); }, editEngine: (data, postMessage) => { snapshotEnginePowerProgression(Object.keys(data?.engines || {}), 'pre_engine_edit'); editEngines(data.engines) postMessage({ responseMessage: "Engines updated", noti_msg: "Succesfully edited all engines performance", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const engineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); postMessage({ responseMessage: "Season performance fetched", content: [performance, races, engineUpgradeRaceIds] }); const attributes = getAttributesAllTeams(yearData[2]); postMessage({ responseMessage: "Performance fetched", content: [performance[performance.length - 1], attributes] }); }, editContract: (data, postMessage) => { const year = getGlobals().yearIteration; editContract(data.driverID, data.salary, data.year, data.signBonus, data.raceBonus, data.raceBonusPos); futureContract(data.futureTeam, data.driverID, data.futureSalary, data.futureYear, data.futureSignBonus, data.futureRaceBonus, data.futureRaceBonusPos, data.futurePosition, year); postMessage({ responseMessage: "Contract updated", noti_msg: `Succesfully edited ${data.driver}'s contract`, isEditCommand: true, unlocksDownload: true }); }, editCalendar: (data, postMessage) => { const year = getGlobals().yearIteration; editCalendar(year, data.racesData); postMessage({ responseMessage: "Calendar updated", noti_msg: "Succesfully updated the calendar", isEditCommand: true, unlocksDownload: true }); }, editRegulations: (data, postMessage) => { updateRegulations(data); const regulations = fetchRegulationsData(); postMessage({ responseMessage: "Regulations fetched", content: regulations, noti_msg: "Succesfully updated regulations", isEditCommand: true, unlocksDownload: true }); }, regulationsRefresh: (data, postMessage) => { const regulations = fetchRegulationsData(); postMessage({ responseMessage: "Regulations fetched", content: regulations }); }, configUpdate: (data, postMessage) => { updateCustomConfig(data); postMessage({ responseMessage: "Config updated", noti_msg: "Succesfully updated the configuration", unlocksDownload: true }); }, fireDriver: (data, postMessage) => { fireDriver(data.driverID, data.teamID); postMessage({ responseMessage: "Driver fired", noti_msg: `Succesfully fired ${data.driver} from ${data.team}`, isEditCommand: true, unlocksDownload: true }); }, hireDriver: (data, postMessage) => { hireDriver("hire", data.driverID, data.teamID, data.position, data.salary, data.signBonus, data.raceBonus, data.raceBonusPos, data.year, getGlobals().yearIteration); postMessage({ responseMessage: "Driver hired", noti_msg: `Succesfully hired ${data.driver} to ${data.team}`, isEditCommand: true, unlocksDownload: true }); }, juniorTransfer(data, postMessage) { transferJuniorDriver(data.driverID, data.teamID, data.posInTeam, getGlobals().yearIteration); postMessage({ responseMessage: "Junior driver transferred", noti_msg: `Succesfully transferred ${data.driver} to ${data.team}`, isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); }, autoContract: (data, postMessage) => { hireDriver("auto", data.driverID, data.teamID, data.position, getGlobals().yearIteration); postMessage({ responseMessage: "Driver hired", noti_msg: `Succesfully hired ${data.driver} to ${data.team}`, isEditCommand: true, unlocksDownload: true }); }, swapDrivers: (data, postMessage) => { swapDrivers(data.driver1ID, data.driver2ID); postMessage({ responseMessage: "Drivers swapped", noti_msg: `Succesfully swapped ${data.driver1} and ${data.driver2}`, isEditCommand: true, unlocksDownload: true }); }, timeTravel: (data, postMessage) => { timeTravelWithData(data.dayNumber, false, data.mod); if (data.mod === "2026"){ changeDriverNumbers2026(); } postMessage({ responseMessage: "Time travel", isEditCommand: true, unlocksDownload: true }); }, changeLineUps: (data, postMessage) => { if (data.mod === "2025"){ changeDriverLineUps(); manageAffiliates(); manageFeederSeries(); changeDriverEngineerPairs(); } else if (data.mod === "2026"){ changeLineUps2026(); } postMessage({ responseMessage: "Line ups changed", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); const staff = fetchStaff(yearData[0]); postMessage({ responseMessage: "Staff fetched", content: staff }); }, driversRefresh: (data, postMessage) => { const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); }, calendarRefresh: (data, postMessage) => { const calendar = fetchCalendar(); postMessage({ responseMessage: "Calendar fetched", content: calendar }); }, changeStats: (data, postMessage) => { if (data.mod === "2025"){ changeStats(); } else if (data.mod === "2026"){ changeStats2026(); } postMessage({ responseMessage: "Stats changed", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); const staff = fetchStaff(yearData[0]); postMessage({ responseMessage: "Staff fetched", content: staff }); }, changeCfd: (data, postMessage) => { if (data.mod === "2025"){ change2024Standings(data.mod); } else if (data.mod === "2026"){ change2024Standings(data.mod); change2025Standings(data.mod); } postMessage({ responseMessage: "CFD times changed", isEditCommand: true, unlocksDownload: true }); }, changeRegulations: (data, postMessage) => { removeFastestLap(data.mod); if (data.mod === "2026"){ changeAdditionalRegulations2026(); } postMessage({ responseMessage: "Regulations changed", isEditCommand: true, unlocksDownload: true }); }, changeCalendar: (data, postMessage) => { if (data.mod === "2025") { changeRaces(data.type); } else if (data.mod === "2026") { updateCalendar2026(data.type); } postMessage({ responseMessage: "Calendar changed", isEditCommand: true, unlocksDownload: true }); const calendar = fetchCalendar(); postMessage({ responseMessage: "Calendar fetched", content: calendar }); }, extraDrivers: (data, postMessage) => { if (data.mod === "2025") { insertStaff2025(); } else if (data.mod === "2026") { insertStaff2026(); } postMessage({ responseMessage: "Extra drivers added", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const drivers = fetchDrivers(yearData[0]); postMessage({ responseMessage: "Drivers fetched", content: drivers }); const staff = fetchStaff(yearData[0]); postMessage({ responseMessage: "Staff fetched", content: staff }); }, changePerformance: (data, postMessage) => { if (data.mod === "2025") { updatePerofmrnace2025(); } else if (data.mod === "2026") { updatePerofmrnace2026(); } postMessage({ responseMessage: "Performance changed", isEditCommand: true, unlocksDownload: true }); const yearData = checkYearSave(); const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const aduoEngineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); const performanceResponse = { responseMessage: "Season performance fetched", content: [performance, races, aduoEngineUpgradeRaceIds] }; postMessage(performanceResponse); const attibutes = getAttributesAllTeams(yearData[2]); const attributesResponse = { responseMessage: "Performance fetched", content: [performance[performance.length - 1], attibutes] }; postMessage(attributesResponse); const carPerformance = getPerformanceAllCars(yearData[2]); const carAttributes = getAttributesAllCars(yearData[2]); const carPerformanceResponse = { responseMessage: "Cars fetched", content: [carPerformance, carAttributes], isEditCommand: true, unlocksDownload: true }; postMessage(carPerformanceResponse); }, performanceRefresh: (data, postMessage) => { const yearData = checkYearSave(); const [performance, races] = getPerformanceAllTeamsSeason(yearData[2], { useHistoricalEnginePower: true }); const aduoEngineUpgradeRaceIds = getAduoEngineUpgradeRaceIds(); const performanceResponse = { responseMessage: "Season performance fetched", content: [performance, races, aduoEngineUpgradeRaceIds] }; postMessage(performanceResponse); const attibutes = getAttributesAllTeams(yearData[2]); const attributesResponse = { responseMessage: "Performance fetched", content: [performance[performance.length - 1], attibutes] }; postMessage(attributesResponse); const carPerformance = getPerformanceAllCars(yearData[2]); const carAttributes = getAttributesAllCars(yearData[2]); const carPerformanceResponse = { responseMessage: "Cars fetched", content: [carPerformance, carAttributes], isEditCommand: true, unlocksDownload: true }; postMessage(carPerformanceResponse); }, generateNews: (data, postMessage) => { try { const savedNewsMap = loadNewsMapFromDB(); // ← desde DB const tpStateFromDB = loadTPFromDB(); // ← desde DB // si necesitas asegurar estructura mínima de TP, hazlo aquí const tpState = ensureTurningPointsStructure(tpStateFromDB); const { newsList, turningPointState } = generate_news(savedNewsMap, tpState); const doublePointsBug = checkDoublePointsBug(turningPointState) const yearsAvailable = getNewsAndTpYearsAvailable() postMessage({ responseMessage: "News fetched", noti_msg: "News generated successfully", content: { newsList, turningPointState, yearsAvailable, doublePointsBug }, unlocksDownload: true }); } catch (e) { console.error("ERROR COMPLETO:", e); console.error("STACK:", e.stack); postMessage({ responseMessage: "Error", error: e.message }); } }, getCustomNewsOptions: (data, postMessage) => { const options = getCustomNewsOptions(); postMessage({ responseMessage: "Custom news options", content: options }); }, customNewsRaceDrivers: (data, postMessage) => { const raceId = Number(data?.raceId); const drivers = getRaceDriversForCustomNews(raceId); postMessage({ responseMessage: "Custom news race drivers", content: drivers }); }, createCustomNews: (data, postMessage) => { try { const entry = createCustomNewsEntry(data || {}); entry.stableKey = entry.stableKey ?? computeStableKey(entry); upsertNews([entry]); postMessage({ responseMessage: "Custom news created", noti_msg: "Custom news created", content: entry, isEditCommand: true, unlocksDownload: true }); } catch (e) { console.error(e); postMessage({ responseMessage: "Error", error: e.message, unlocksDownload: true }); } }, fixDoublePointsBug: (data, postMessage) => { const raceBugged = data.raceId; fixDoublePointsBug(raceBugged); postMessage({ responseMessage: "Double points bug fixed", noti_msg: "Double points bug fixed successfully", unlocksDownload: true }); }, getNewsFromSeason: (data, postMessage) => { const season = data.season; let newsAndTp = getNewsFromSeason(season); const currentSeason = getGlobals().currentDate[1]; newsAndTp.isCurrentSeason = (season == currentSeason); postMessage({ responseMessage: "News from season fetched", content: newsAndTp }); }, lineupsRequest: (data, postMessage) => { const lineups = getCurrentAndNextSeasonGridLineups(); const season = Number(lineups?.season) || 0; const previousSeason = season > 0 ? (season - 1) : 0; const previousSeasonStandings = previousSeason > 0 ? fetchTeamsStandings(previousSeason, 1) : []; postMessage({ responseMessage: "Lineups fetched", content: { ...lineups, previousSeason, previousSeasonStandings } }); }, updateCombinedDict: (data, postMessage) => { const teamId = data.teamID; const newName = data.newName; combined_dict[teamId] = newName; postMessage({ responseMessage: "Combined dict updated", content: combined_dict }); }, raceDetailsRequest: (data, postMessage) => { const raceId = data.raceid; const results = getOneRaceDetails(raceId); postMessage({ responseMessage: "Race details fetched", content: results }); }, qualiDetailsRequest: (data, postMessage) => { const qualiId = data.raceid; const results = getOneQualiDetails(qualiId); postMessage({ responseMessage: "Quali details fetched", content: results }); }, transferRumorRequest: (data, postMessage) => { const drivers = data.drivers; const date = data.date || null; // New date parameter const info = getTransferDetails(drivers, date) postMessage({ responseMessage: "Transfer details fetched", content: info }); }, teamComparisonRequest: (data, postMessage) => { const teamId = data.team; const season = data.season; const date = data.date; const results = getTeamComparisonDetails(teamId, season, date); postMessage({ responseMessage: "Team comparison details fetched", content: results }); }, fullChampionshipDetailsRequest: (data, postMessage) => { const season = data.season; const junior = data.junior || false; const results = getFullChampionSeasonDetails(season); postMessage({ responseMessage: "Full championship details fetched", content: results }); }, fullFeederSeriesDetailsRequest: (data, postMessage) => { const season = data.season; const results = getFullFeederSeriesDetails(season, true); postMessage({ responseMessage: "Full feeder series details fetched", content: results }); }, recordSelected: (data, postMessage) => { const type = data.type; const year = data.year; const record = getSelectedRecord(type, year); postMessage({ responseMessage: "Record fetched", content: record }); }, teamRecordRequest: (data, postMessage) => { const type = data.type; const year = data.year; const formula = data.formula ? Number(data.formula) : 1; const record = getSelectedTeamRecord(type, year, formula); postMessage({ responseMessage: "Team record fetched", content: { type, year, formula, record } }); }, seasonReviewSelected: (data, postMessage) => { const year = data.year; const formula = data.formula ? Number(data.formula) : 1; const globals = getGlobals(); const isCurrentYear = data.isCurrentYear ?? (String(globals?.yearIteration) === String(year)); const review = fetchSeasonReviewData(year, formula, isCurrentYear); postMessage({ responseMessage: "Season review data fetched", content: review }); }, approveTurningPoint: (data, postMessage) => { const turningPointData = data.turningPointData; const type = data.type; const maxDate = data.maxDate; const originalStableKey = data.id; const nonReadable = data.nonReadable || false; const newResponse = generateTurningResponse(turningPointData, type, maxDate, "positive"); if (originalStableKey) { updateNewsFields(originalStableKey, { turning_point_type: "approved", ...(nonReadable ? { nonReadable: true } : {}) }); } if (newResponse) { newResponse.stableKey = newResponse.stableKey ?? computeStableKey(newResponse); upsertNews([newResponse]); } postMessage({ responseMessage: "Turning point positive", content: newResponse, isEditCommand: true, unlocksDownload: true }); }, cancelTurningPoint: (data, postMessage) => { const turningPointData = data.turningPointData; const type = data.type; const maxDate = data.maxDate; const originalStableKey = data.id; const newResponse = generateTurningResponse(turningPointData, type, maxDate, "negative"); if (originalStableKey) { updateNewsFields(originalStableKey, { turning_point_type: "cancelled" }); } if (newResponse) { newResponse.stableKey = newResponse.stableKey ?? computeStableKey(newResponse); upsertNews([newResponse]); } postMessage({ responseMessage: "Turning point negative", noti_msg: "Cancelled turning point", content: newResponse, isEditCommand: true, unlocksDownload: true }); }, saveNewsState: (data, postMessage) => { try { upsertNews(data.newsList || []); postMessage({ responseMessage: "News saved", noti_msg: "News saved successfully", isEditCommand: true, unlocksDownload: true }); } catch (e) { console.error(e); postMessage({ responseMessage: "Error", error: e.message, unlocksDownload: true }); } }, updateNews: (data, postMessage) => { try { const ok = updateNewsFields( data.stableKey, data.patch || {} // { text, nonReadable, turning_point_type, ... } ); postMessage({ responseMessage: ok ? "News updated" : "News not found", noti_msg: ok ? "News updated successfully" : "News not found", isEditCommand: true, unlocksDownload: true }); } catch (e) { console.error(e); postMessage({ responseMessage: "Error", error: e.message, unlocksDownload: true }); } }, getNews: (data, postMessage) => { const newsMap = loadNewsMapFromDB(); postMessage({ responseMessage: "News map", content: newsMap }); }, saveTurningPoints: (data, postMessage) => { try { upsertTurningPoints(data.turningPointState || {}); postMessage({ responseMessage: "Turning points saved successfully" }); } catch (e) { console.error(e); postMessage({ responseMessage: "Error", error: e.message, unlocksDownload: true }); } }, getTurningPoints: (data, postMessage) => { const turningPoints = loadTPFromDB(); postMessage({ responseMessage: "Turning points", content: turningPoints }); }, migrateFromLocalStorage: (data, postMessage) => { try { const { lsNewsTxt, lsTPTxt } = data || {}; const res = migrateLegacyData(lsNewsTxt, lsTPTxt); postMessage({ responseMessage: "Migration done", status: res, noti_msg: "Migration completed successfully", isEditCommand: true, unlocksDownload: true }); } catch (e) { console.error("Migration error (worker):", e); postMessage({ responseMessage: "Error", error: e.message }); } }, deleteNewsArticle: (data, postMessage) => { const articleId = data.articleId; const ok = deleteNewByKey(articleId); postMessage({ responseMessage: ok ? "Article deleted successfully" : "Article not found", noti_msg: ok ? "Article deleted successfully" : "Article not found", isEditCommand: true, unlocksDownload: true }); }, deleteNews: (data, postMessage) => { deleteNews(); deleteTurningPoints(); postMessage({ responseMessage: "News deleted successfully", unlocksDownload: true }); }, enginesRefresh: (data, postMessage) => { const engines = fetchEngines(); postMessage({ responseMessage: "Engines fetched", noti_msg: "Engines updated successfully", content: engines }); }, eventsFromRequest: (data, postMessage) => { const year = data.year; const formula = data.formula ? Number(data.formula) : 1; const events = fetchEventsFrom(year, formula); postMessage({ responseMessage: "Events fetched", content: { year, formula, events } }); }, sessionResultsRequest: (data, postMessage) => { const year = data.year; const gameYear = data.gameYear; const raceId = data.raceId; const sessionKey = data.sessionKey; const payload = fetchSessionResults(raceId, sessionKey, gameYear); postMessage({ responseMessage: "Session results fetched", content: { year, raceId, sessionKey, ...payload } }); }, editRaceResults: (data, postMessage) => { const raceId = data.raceId; const edits = data.edits; const sessionKey = data?.sessionKey; const isSprint = data?.isSprint; const res = editRaceResults(raceId, edits, { sessionKey, isSprint }); postMessage({ responseMessage: res.ok ? "Race results updated" : "Error", noti_msg: res.ok ? "Race results updated" : (res.error || "Failed to update race results"), unlocksDownload: true, isEditCommand: true }); }, pointsRegulationsRequest: (data, postMessage) => { const pointsInfo = fetchPointsRegulations(); postMessage({ responseMessage: "Points regulations fetched", content: pointsInfo }); }, add2026Engines: (data, postMessage) => { apply2026EnginePerformanceChanges(); const engines = fetchEngines(); postMessage({ responseMessage: "Engines fetched", content: engines }); postMessage({ responseMessage: "2026 engines added", isEditCommand: true, unlocksDownload: true }); }, updateAduoTPEnabled: (data, postMessage) => { const enabled = data.enabled; setCustomSaveConfig("aduo_tp_enabled", enabled); postMessage({ responseMessage: "ADUO TP enabled updated", noti_msg: `ADUO TP enabled set to ${enabled}`, isEditCommand: true, unlocksDownload: true }); } }; self.addEventListener('message', async (e) => { console.log(e.data); const { command, data } = e.data; if (workerCommands[command]) { try { await workerCommands[command](data, (response) => postMessage({ command, ...response })); } catch (error) { console.error(`[Worker] Error executing command '${command}':`, error); postMessage({ command, responseMessage: "Error", error: error.message }); } } else { console.error(`[Worker] Unknown command: '${command}'`); postMessage({ responseMessage: "Unknown command", command }); } }); ================================================ FILE: src/js/frontend/calendar.js ================================================ import { races_map, codes_dict, weather_dict, countries_dict, invertedRacesMap } from "./config"; import { game_version } from "./renderer"; import { max_races } from "./head2head"; import interact from 'interactjs'; let deleting = false; let deleted = false; let previewTarget = null; let previewPosition = null; /** * Positions both the div the user's moving and the one he has moved it into * @param {div} div0 The div the user is moving * @param {div} div1 The div the user has moved div0 into * @param {string} beforeAfter If the user has moved div0 before or after div1 */ function reubicate(div0,div1,beforeAfter) { const parentDiv = document.querySelector('.main-calendar-section'); parentDiv.removeChild(div0) if (beforeAfter === 'before') { parentDiv.insertBefore(div0,div1); } else if (beforeAfter === 'after') { parentDiv.insertBefore(div0,div1.nextSibling); } } /** * Adds a race in the calendar div * @param {Object} race */ function addRace(race) { const { code, rainP, rainQ, rainR, type, trackId, state, isF2Race = 0, isF3Race = 0, } = race; let imageUrl = codes_dict[code]; let div = document.createElement('div'); let leftDiv = document.createElement('div'); let numberDiv = document.createElement('div'); numberDiv.className = "race-calendar-number bold-font" leftDiv.className = "left-race" let rightDiv = document.createElement('div'); rightDiv.className = "right-race" div.classList.add('race-calendar'); div.dataset.trackid = trackId div.dataset.rainQ = rainQ div.dataset.rainR = rainR div.dataset.rainP = rainP div.dataset.type = type div.dataset.state = state div.dataset.isf2 = isF2Race div.dataset.isf3 = isF3Race if(state === 2){ div.classList.add("completed"); } const seriesBadges = document.createElement('div'); seriesBadges.className = "race-series-badges"; const f2Badge = document.createElement('button'); f2Badge.className = "race-series-badge race-series-badge-f2 bold-font"; f2Badge.type = "button"; f2Badge.textContent = "F2"; seriesBadges.appendChild(f2Badge); const f3Badge = document.createElement('button'); f3Badge.className = "race-series-badge race-series-badge-f3 bold-font"; f3Badge.type = "button"; f3Badge.textContent = "F3"; seriesBadges.appendChild(f3Badge); if (Number(isF2Race) === 1) { f2Badge.classList.add("active-badge"); } if (Number(isF3Race) === 1) { f3Badge.classList.add("active-badge"); } f2Badge.addEventListener("click", function () { if (div.dataset.isf2 === "1") { div.dataset.isf2 = "0"; f2Badge.classList.remove("active-badge"); } else { div.dataset.isf2 = "1"; f2Badge.classList.add("active-badge"); } }); f3Badge.addEventListener("click", function () { if (div.dataset.isf3 === "1") { div.dataset.isf3 = "0"; f3Badge.classList.remove("active-badge"); } else { div.dataset.isf3 = "1"; f3Badge.classList.add("active-badge"); } }); if (seriesBadges.childElementCount > 0) { div.appendChild(seriesBadges); } let upperDiv = document.createElement('div'); upperDiv.className = "upper-text-and-flag" let textDiv = document.createElement('div'); textDiv.classList.add('upper-race','bold-font'); textDiv.textContent = code.slice(0,-1).toUpperCase(); const img = document.createElement('img'); img.src = imageUrl; img.classList.add('flag'); img.setAttribute("loading","lazy"); upperDiv.appendChild(textDiv); upperDiv.appendChild(img); let ATAInput; let lowerDiv = document.createElement('div'); lowerDiv.classList.add('lower-race'); lowerDiv.innerHTML = "
"; if (game_version === 2023){ lowerDiv.innerHTML += "
"; ATAInput = lowerDiv.querySelector(".ata-input") } let SprintInput = lowerDiv.querySelector(".sprint-input") SprintInput.addEventListener("click",function (event) { if (game_version === 2023){ if (ATAInput.checked) ATAInput.checked = false } if (SprintInput.checked) div.dataset.type = 1 else div.dataset.type = 0 }) if (game_version === 2023){ ATAInput.addEventListener("click",function (event) { if (SprintInput.checked) SprintInput.checked = false if (ATAInput.checked) div.dataset.type = 2 else div.dataset.type = 0 }) } leftDiv.appendChild(upperDiv); leftDiv.appendChild(lowerDiv); if(type === 1){ lowerDiv.children[0].firstChild.click() } else if(type === 2){ lowerDiv.children[1].firstChild.click() } numberDiv.addEventListener("click", function () { if (!deleting) return; const raceDiv = numberDiv.closest(".race-calendar"); if (!raceDiv) return; raceDiv.parentNode.removeChild(raceDiv); deleted = true; if (raceDiv.dataset.trackid === "6"){ update_notifications("Why'd you do that?", "monaco"); } update_numbers(); }); div.appendChild(numberDiv) div.appendChild(leftDiv) let qWeather = document.createElement('div'); qWeather.className = "full-quali-weather" let qName = document.createElement('div'); qName.className = "session-name bold-font" qName.innerText ="Sat" let wSelector = document.createElement('div'); wSelector.className = "weather-selector" let leftArrow = document.createElement('i'); leftArrow.classList.add("bi", "bi-chevron-left", "new-augment-button", "transparent") let rightArrow = document.createElement('i'); rightArrow.classList.add("bi", "bi-chevron-right", "new-augment-button", "transparent") let wVis = document.createElement('div'); wVis.className = "weather-vis" wVis.dataset.value = Number(rainQ) wSelector.appendChild(leftArrow) wSelector.appendChild(wVis) wSelector.appendChild(rightArrow) qWeather.appendChild(qName) qWeather.appendChild(wSelector) let rWeather = qWeather.cloneNode(true) rWeather.firstChild.innerText = "Sun" rWeather.children[1].children[1].dataset.value = Number(rainR) let pWeather = qWeather.cloneNode(true) pWeather.firstChild.innerText = "Fri" pWeather.children[1].children[1].dataset.value = Number(rainP) rightDiv.appendChild(pWeather) rightDiv.appendChild(qWeather) rightDiv.appendChild(rWeather) div.appendChild(rightDiv) div.querySelectorAll(".bi-chevron-left").forEach(function(elem){ elem.addEventListener("click", function(){ let val = elem.parentNode.querySelector(".weather-vis").dataset.value let newVal = Number(val) - 1 if(newVal === -1){ newVal = 5 } elem.parentNode.querySelector(".weather-vis").dataset.value = newVal if (elem.parentNode.parentNode.firstChild.innerText === "Sat"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainQ = newVal } else if (elem.parentNode.parentNode.firstChild.innerText === "Sun"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainR = newVal } else if (elem.parentNode.parentNode.firstChild.innerText === "Fri"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainP = newVal } updateVisualizers() }) }) div.querySelectorAll(".bi-chevron-right").forEach(function(elem){ elem.addEventListener("click", function(){ let val = elem.parentNode.querySelector(".weather-vis").dataset.value let newVal = Number(val) + 1 if(newVal === 6){ newVal = 0 } elem.parentNode.querySelector(".weather-vis").dataset.value = newVal if (elem.parentNode.parentNode.firstChild.innerText === "Sat"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainQ = newVal } else if (elem.parentNode.parentNode.firstChild.innerText === "Sun"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainR = newVal } else if (elem.parentNode.parentNode.firstChild.innerText === "Fri"){ elem.parentNode.parentNode.parentNode.parentNode.dataset.rainP = newVal } updateVisualizers() }) }) document.querySelector('.main-calendar-section').appendChild(div) } function updateVisualizers(){ document.querySelector(".main-calendar").querySelectorAll(".weather-vis").forEach(function(elem){ elem.innerHTML = "" let val = elem.dataset.value let icon = document.createElement("i") icon.className = weather_dict[val] elem.appendChild(icon) }) } export function load_calendar(races){ document.querySelector('.main-calendar-section').innerHTML = "" races.forEach(function(elem){ addRace({ code: races_map[elem.trackId], rainP: transformWeather(elem.weatherStatePractice), rainQ: transformWeather(elem.weatherStateQualifying), rainR: transformWeather(elem.weatherStateRace), type: elem.weekendType, trackId: elem.trackId, state: elem.state, isF2Race: elem.isF2Race, isF3Race: elem.isF3Race }) }) updateVisualizers() update_numbers() load_addRaces() } function update_numbers(){ document.querySelectorAll(".race-calendar").forEach(function(elem, index){ updateNumberDisplay(elem, index); }) } function updateNumberDisplay(race, index) { const numberDiv = race.querySelector(".race-calendar-number"); if (!numberDiv) return; numberDiv.innerHTML = ""; numberDiv.classList.remove("race-calendar-number-completed", "race-calendar-number-delete"); if (race.classList.contains("completed")) { const icon = document.createElement("i"); icon.className = "bi bi-check-lg"; numberDiv.classList.add("race-calendar-number-completed"); numberDiv.appendChild(icon); return; } if (deleting) { const icon = document.createElement("i"); icon.className = "bi bi-trash-fill"; numberDiv.classList.add("race-calendar-number-delete"); numberDiv.appendChild(icon); return; } numberDiv.textContent = index + 1; } function clearDropPreview() { if (previewTarget) { previewTarget.classList.remove("drop-before", "drop-after"); } previewTarget = null; previewPosition = null; } function transformWeather(state){ let realWeather; if(state === 1){ realWeather = 0 } else if(state === 2){ realWeather = 1 } else if(state === 4){ realWeather = 2 } else if(state === 8){ realWeather = 3 } else if(state === 16){ realWeather = 4 } else if(state === 32){ realWeather = 5 } return realWeather; } /** * Changes the number after the race code to specify the format * @param {div} div div from the race that's changing format * @param {string} format code of the format */ function changeFormat(div,format) { let lastChar = div.dataset.code.charAt(div.dataset.code.length - 1) if (/\d/.test(lastChar)) { div.dataset.code = div.dataset.code.slice(0,-1) + format } else { div.dataset.code = div.dataset.code + format } } /** * Adds all the races to the addRace menu */ function load_addRaces() { document.getElementById("addTrackMenu").innerHTML = "" for (let dataCode of Object.keys(codes_dict)) { let elem = countries_dict[dataCode] let li = document.createElement('li'); let a = document.createElement('a'); a.classList.add('redesigned-dropdown-item'); a.classList.add('menu-race'); a.href = '#'; a.textContent = elem; a.dataset.code = dataCode a.dataset.trackid = invertedRacesMap[dataCode] let imageUrl = codes_dict[dataCode]; let img = document.createElement('img'); img.src = imageUrl; img.classList.add('menuFlag'); img.setAttribute("loading","lazy"); a.appendChild(img) li.appendChild(a); document.getElementById("addTrackMenu").appendChild(li); } listenerRaces() } /** * Adds the listeners to the addRace menu races */ function listenerRaces() { document.querySelectorAll('#addTrackMenu a').forEach(item => { item.addEventListener("click",function () { if (document.querySelector(".main-calendar-section").childElementCount < max_races) { addRace({ code: item.dataset.code, rainP: 0, rainQ: 0, rainR: 0, type: 0, trackId: item.dataset.trackid, state: 0, isF2Race: 0, isF3Race: 0 }) updateVisualizers() update_numbers() } }) }) } /** * Event listeenr for the delete tracks button */ document.getElementById("deleteTracks").addEventListener("click",function (btn) { if (deleting) { this.className = "close-modal" document.querySelectorAll(".race-calendar").forEach(function (elem) { elem.classList.remove("deleting"); }) } else { document.querySelectorAll(".race-calendar").forEach(function (elem) { elem.classList.add("deleting"); }) this.className = "close-modal delete-mode" } deleting = !deleting update_numbers(); }) /** * Manages the interaction with the race divs */ interact('.race-calendar').draggable({ inertia: true, listeners: { start(event) { let target = event.target; if (target.classList.contains("completed")) { event.interaction.stop(); return; } let position = target.getBoundingClientRect(); let width = target.getBoundingClientRect().width }, move(event) { const target = event.target; if (target.classList.contains("completed")) return; const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; target.style.transform = `translate(${x}px, ${y}px)`; target.style.opacity = 1; target.style.zIndex = 10; target.setAttribute('data-x',x); target.setAttribute('data-y',y); const racesEvents = document.querySelectorAll('.race-calendar'); let nextTarget = null; let nextPosition = null; racesEvents.forEach(function (element) { if (target === element) return; let eventRect = element.getBoundingClientRect(); let centerHorizontal = (eventRect.left + eventRect.right) / 2; if (event.clientX >= eventRect.left && event.clientX <= eventRect.right && event.clientY >= eventRect.top && event.clientY <= eventRect.bottom) { nextTarget = element; nextPosition = event.clientX >= centerHorizontal ? "after" : "before"; } }); if (previewTarget !== nextTarget || previewPosition !== nextPosition) { clearDropPreview(); if (nextTarget && nextPosition) { previewTarget = nextTarget; previewPosition = nextPosition; previewTarget.classList.add(nextPosition === "after" ? "drop-after" : "drop-before"); } } }, end(event) { let target = event.target; if (target.classList.contains("completed")) return; if (previewTarget && previewPosition) { reubicate(target, previewTarget, previewPosition); update_numbers(); } clearDropPreview(); target.style.transform = 'none'; target.setAttribute('data-x',0); target.setAttribute('data-y',0); // originalParent = undefined; // destinationParent = undefined; // draggable = undefined; } } }) ================================================ FILE: src/js/frontend/config.js ================================================ // calendar export let codes_dict = { "bah0": "../assets/images/bahrain.png", "sau0": "../assets/images/saudi.jpg", "aus0": "../assets/images/australia.png", "aze0": "../assets/images/azerbaiyan.png", "mia0": "../assets/images/usa.png", "imo0": "../assets/images/italy.png", "mon0": "../assets/images/monaco.png", "spa0": "../assets/images/spain.png", "can0": "../assets/images/canada.png", "aut0": "../assets/images/austria.png", "gbr0": "../assets/images/gbr.png", "hun0": "../assets/images/hungry.png", "bel0": "../assets/images/balgium.png", "ned0": "../assets/images/ned.png", "ita0": "../assets/images/italy.png", "jap0": "../assets/images/japan.png", "sgp0": "../assets/images/singapore.png", "qat0": "../assets/images/qatar.png", "usa0": "../assets/images/usa.png", "mex0": "../assets/images/mexico.png", "bra0": "../assets/images/brazil.png", "veg0": "../assets/images/usa.png", "uae0": "../assets/images/uae.png", "chi0": "../assets/images/china.png" } export let countries_dict = { "bah0": "Bahrain", "sau0": "Saudi Arabia", "aus0": "Australia", "aze0": "Azerbaijan", "mia0": "Miami", "imo0": "Imola", "mon0": "Monaco", "spa0": "Spain", "can0": "Canada", "aut0": "Austria", "gbr0": "United Kingdom", "hun0": "Hungary", "bel0": "Belgium", "ned0": "Netherlands", "ita0": "Italy", "sgp0": "Singapore", "jap0": "Japan", "qat0": "Qatar", "usa0": "USA", "mex0": "Mexico", "bra0": "Brazil", "veg0": "Vegas", "uae0": "Abu Dhbai", "chi0": "China" }; export let weather_dict = { 0: "bi bi-sun", 1: "bi bi-cloud-sun", 2: "bi bi-cloud", 3: "bi bi-cloud-drizzle", 4: "bi bi-cloud-rain", 5: "bi bi-cloud-rain-heavy" } //h2h export const lightColors = ["#f1f1f1", "#47c7fc", "#ffd300", "#6CD3BF", "#fcfcfc", "#37BEDD", "#B6BABD", "#c3dc00", "#d0e610", "#fac51c", "#b09247", "#f7c82f"] export const default_dict = { 1: "Ferrari", 2: "McLaren", 3: "Red Bull", 4: "Mercedes", 5: "Alpine", 6: "Williams", 7: "Haas", 8: "Alpha Tauri", 9: "Alfa Romeo", 10: "Aston Martin", 32: "Custom Team" } export let combined_dict = { 1: "Ferrari", 2: "McLaren", 3: "Red Bull", 4: "Mercedes", 5: "Alpine", 6: "Williams", 7: "Haas", 8: "Alpha Tauri", 9: "Alfa Romeo", 10: "Aston Martin", 11: "Prema Racing (F2)", 12: "Virtuosi Racing (F2)", 13: "Rodin (F2)", 14: "Hitech GP (F2)", 15: "ART Grand Prix (F2)", 16: "MP Motorsport (F2)", 17: "PHM Racing (F2)", 18: "DAMS (F2)", 19: "Campos Racing (F2)", 20: "VAR Racing (F2)", 21: "Trident (F2)", 22: "Prema Racing (F3)", 23: "Trident (F3)", 24: "ART Grand Prix (F3)", 25: "Hitech GP (F3)", 26: "VAR Racing (F3)", 27: "MP Motorsport (F3)", 28: "Campos Racing (F3)", 29: "Rodin (F3)", 30: "Jenzer Motorsport (F3)", 31: "PHM Racing (F3)", 32: "Custom Team" } export function getUpdatedName(teamId) { return combined_dict[teamId] } export function getCombinedDict() { return combined_dict } //performance export const pars_abreviations = { "chassis": "C", "front_wing": "FW", "rear_wing": "RW", "underfloor": "UF", "sidepods": "SP", "suspension": "S" } export const part_codes_abreviations = { 3: "C", 4: "FW", 5: "RW", 6: "UF", 7: "SP", 8: "S" } export const part_full_names = { 3: "Chassis", 4: "Front Wing", 5: "Rear Wing", 6: "Underfloor", 7: "Sidepods", 8: "Suspension" } export let abreviations_dict = { 1: "FE", 2: "MC", 3: "RB", 4: "MER", 5: "ALP", 6: "WIL", 7: "HA", 8: "AT", 9: "ALFA", 10: "AM", 32: "CUS" } export let engine_stats_dict = new Map([ [10, "Power"], [6, "Fuel efficiency"], [11, "Performance threshold"], [12, "Performance loss"], [14, "Engine durability"], [18, "ERS durability"], [19, "Gearbox durability"] ]); export const theme_colors = { "default-theme": { "labels": "#dedde6", "grid": "#292929", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "light-theme": { "labels": "#252525", "grid": "#d6d6d6", "general_secondary": "#1f1f1f", "engine_upgrade_line": "#a18f45cc" }, "og-theme": { "labels": "#dedde6", "grid": "#323046", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "vaporwave-theme": { "labels": "#dedde6", "grid": "#5329b5", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "nightly-theme": { "labels": "#dedde6", "grid": "#292929", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "ferrari-theme": { "labels": "#f0e6e8", "grid": "#5a3a42", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "redbull-theme": { "labels": "#d8e5ff", "grid": "#39578f", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "mercedes-theme": { "labels": "#c8f9f2", "grid": "#2a515c", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "astonmartin-theme": { "labels": "#e8f3ee", "grid": "#2a5a4a", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "mclaren-theme": { "labels": "#f7e6dc", "grid": "#5a4033", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "audi-theme": { "labels": "#f0e6e8", "grid": "#4a1f27", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "vcarb-theme": { "labels": "#d8e2ff", "grid": "#2b3f6b", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "williams-theme": { "labels": "#d7e8ff", "grid": "#173a70", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "haas-theme": { "labels": "#e7e7ea", "grid": "#3c3c4c", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" }, "alpine-theme": { "labels": "#ffe0f2", "grid": "#5a3a5f", "general_secondary": "#f1f1f1", "engine_upgrade_line": "#ffe16acc" } } export const themeToolbarLogos = { "nightly-theme": { src: "../assets/images/logoNightly.svg", className: "toolbar-logo--nightly" }, "ferrari-theme": { src: "../assets/images/logos/ferrari.png", className: "toolbar-logo--ferrari" }, "redbull-theme": { src: "../assets/images/logos/redbull.png", className: "toolbar-logo--redbull" }, "mercedes-theme": { src: "../assets/images/logos/mercedes.png", className: "toolbar-logo--mercedes" }, "astonmartin-theme": { src: "../assets/images/logos/astonMartin.png", className: "toolbar-logo--astonmartin" }, "mclaren-theme": { src: "../assets/images/logos/mclaren.png", className: "toolbar-logo--mclaren" }, "audi-theme": { src: "../assets/images/logos/audi.png", className: "toolbar-logo--audi" }, "vcarb-theme": { src: "../assets/images/logos/visarb.png", className: "toolbar-logo--vcarb" }, "williams-theme": { src: "../assets/images/logos/Williams_2026_logo.svg", className: "toolbar-logo--williams" }, "haas-theme": { src: "../assets/images/logos/haas.png", className: "toolbar-logo--haas" }, "alpine-theme": { src: "../assets/images/logos/alpine.png", className: "toolbar-logo--alpine" }, }; //predictions export const names_full = { "BAH": "Bahrain", "AUS": "Australia", "SAU": "Saudi Arabia", "IMO": "Imola", "MIA": "Miami", "SPA": "Spain", "MON": "Monaco", "AZE": "Azerbaijan", "CAN": "Canada", "GBR": "Great Britain", "AUT": "Austria", "FRA": "France", "HUN": "Hungary", "BEL": "Belgium", "ITA": "Italy", "SGP": "Singapore", "JAP": "Japan", "USA": "United States", "MEX": "Mexico", "BRA": "Brazil", "UAE": "Abu Dhabi", "NED": "Netherlands", "VEG": "Vegas", "QAT": "Qatar", "CHI": "China" }; //seasonViewer export let driversTableLogosDict = { "stake": "logo-stake-table", "audi": "logo-up-down-extra", "alfa": "logo-merc-table", "sauber": "logo-sauber-table", "visarb": "logo-visarb-table", "hugo": "logo-hugo-table", "brawn": "logo-brawn-table", "toyota": "logo-toyota-table", "alphatauri": "logo-alphatauri-table", "porsche": "logo-porsche-table", "renault": "logo-renault-table", "andretti": "logo-andretti-table", "lotus": "logo-lotus-table", "alpine": "logo-alpine-table", "cadillac": "logo-cadillac-table", "ford": "logo-ford-table", "racingpoint": "logo-racingpoint-table", "jordan": "logo-jordan-table" } export const races_map = { 2: "bah0", 1: "aus0", 11: "sau0", 24: "imo0", 22: "mia0", 5: "spa0", 6: "mon0", 4: "aze0", 7: "can0", 10: "gbr0", 9: "aut0", 8: "fra0", 12: "hun0", 13: "bel0", 14: "ita0", 15: "sgp0", 17: "jap0", 19: "usa0", 18: "mex0", 20: "bra0", 21: "uae0", 23: "ned0", 25: "veg0", 26: "qat0", 3: "chi0" }; export const invertedRacesMap = { "bah0": 2, "aus0": 1, "sau0": 11, "imo0": 24, "mia0": 22, "spa0": 5, "mon0": 6, "aze0": 4, "can0": 7, "gbr0": 10, "aut0": 9, "fra0": 8, "hun0": 12, "bel0": 13, "ita0": 14, "sgp0": 15, "jap0": 17, "usa0": 19, "mex0": 18, "bra0": 20, "uae0": 21, "ned0": 23, "veg0": 25, "qat0": 26, "chi0": 3 }; export const races_names = { 2: "BAH", 1: "AUS", 11: "SAU", 24: "IMO", 22: "MIA", 5: "SPA", 6: "MON", 4: "AZE", 7: "CAN", 10: "GBR", 9: "AUT", 8: "FRA", 12: "HUN", 13: "BEL", 14: "ITA", 15: "SGP", 17: "JAP", 19: "USA", 18: "MEX", 20: "BRA", 21: "UAE", 23: "NED", 25: "VEG", 26: "QAT", 3: "CHI" }; export const contintntRacesRegions = { "Europe": [24, 5, 6, 10, 9, 12, 13, 14, 23], "Asia": [17, 3, 15, 1], "America": [19, 20, 18, 25, 22], "Middle East": [2, 11, 21, 26, 4], } export const continentDict = { 24: "Europe", 5: "Europe", 6: "Europe", 10: "Europe", 9: "Europe", 8: "Europe", 12: "Europe", 13: "Europe", 14: "Europe", 23: "Europe", 17: "Asia", 3: "Asia", 15: "Asia", 1: "Asia", 19: "America", 20: "America", 18: "America", 25: "America", 22: "America", 2: "Middle East", 11: "Middle East", 21: "Middle East", 26: "Middle East", 4: "Middle East" } export const teams_full_name_dict = { 'FERRARI': 1, 'MCLAREN': 2, 'RED BULL': 3, 'MERCEDES': 4, 'ALPINE': 5, 'WILLIAMS': 6, 'HAAS': 7, 'ALPHA TAURI': 8, 'ALFA ROMEO': 9, 'ASTON MARTIN': 10 } export let logos_disc = { 1: '../assets/images/logos/ferrari.png', 2: '../assets/images/logos/mclaren.png', 3: '../assets/images/logos/redbull.png', 4: '../assets/images/logos/mercedes.png', 5: '../assets/images/logos/alpine.png', 6: '../assets/images/logos/Williams_2026_logo.svg', 7: '../assets/images/logos/haas.png', 8: '../assets/images/logos/alphatauri.png', 9: '../assets/images/logos/alfaromeo.png', 10: '../assets/images/logos/astonMartin.png', 11: '../assets/images/logos/prema.png', 12: '../assets/images/logos/invicta.png', 13: '../assets/images/logos/rodin.png', 14: '../assets/images/logos/hitech.png', 15: '../assets/images/logos/art.png', 16: '../assets/images/logos/mp.png', 17: '../assets/images/logos/phm.png', 18: '../assets/images/logos/dams.png', 19: '../assets/images/logos/campos.png', 20: '../assets/images/logos/var.png', 21: '../assets/images/logos/trident.png', 22: '../assets/images/logos/prema.png', 23: '../assets/images/logos/trident.png', 24: '../assets/images/logos/art.png', 25: '../assets/images/logos/hitech.png', 26: '../assets/images/logos/var.png', 27: '../assets/images/logos/mp.png', 28: '../assets/images/logos/campos.png', 29: '../assets/images/logos/rodin.png', 30: '../assets/images/logos/jenzer.png', 31: '../assets/images/logos/phm.png', 32: '../assets/images/logos/placeholder.png' }; export const points_race = { 1: 25, 2: 18, 3: 15, 4: 12, 5: 10, 6: 8, 7: 6, 8: 4, 9: 2, 10: 1, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0, "DNF": 0 } export const points_sprint = { 1: 8, 2: 7, 3: 6, 4: 5, 5: 4, 6: 3, 7: 2, 8: 1, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0, "-1": 0 } export let default_points = ["25", "18", "15", "12", "10", "8", "6", "4", "2", "1", "DNF", "0", "", "-"] //stats export let typeStaff_dict = { 0: "fulldriverlist", 1: "fullTechnicalList", 2: "fullEngineerList", 3: "fullAeroList", 4: "fullDirectorList" } export let mentality_dict = { 0: "enthusiastic", 1: "positive", 2: "neutral", 3: "negative", 4: "demoralized" } export let teamOrder = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]; export const mentality_bonuses = { 0: 7, 1: 4, 2: 0, 3: -2, 4: -6 } export const mentalityModifiers = { 5: -8, 9: -7, 15: -6, 20: -5, 24: -4, 29: -3, 35: -2, 39: -1, 59: 0, 63: 1, 69: 2, 77: 3, 79: 4, 83: 5, 85: 6, 96: 7, 100: 8 }; export const mentality_to_global_menatality = { 0: 95, 1: 79, 2: 59, 3: 24, 4: 5, } //transfers export const f1_teams = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32] export const f2_teams = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21] export const f3_teams = [22, 23, 24, 25, 26, 27, 28, 29, 30, 31] export const staff_positions = { 1: "technical-chief", 2: "race-engineer", 3: "head-aero", 4: "sporting-director" } export const staff_pics = { 1: "../assets/images/technicalChief.png", 2: "../assets/images/raceEngineer2.png", 3: "../assets/images/headAero.png", 4: "../assets/images/sportingDirector.png" } export let team_dict = { 1: "fe", 2: "mc", 3: "rb", 4: "me", 5: "al", 6: "wi", 7: "ha", 8: "at", 9: "af", 10: "as", 11: "pre", 12: "vir", 13: "car", 14: "hit", 15: "art", 16: "mp", 17: "phm", 18: "dam", 19: "cam", 20: "var", 21: "tri", 22: "pre", 23: "tri", 24: "art", 25: "hit", 26: "var", 27: "mp", 28: "cam", 29: "car", 30: "jen", 31: "phm", 32: "ct", 33: "f2", 34: "f3" } export let inverted_dict = { 'ferrari': 1, 'mclaren': 2, 'redbull': 3, 'merc': 4, 'alpine': 5, 'williams': 6, 'haas': 7, 'alphatauri': 8, 'alfaromeo': 9, 'astonmartin': 10, 'custom': 32 } // news export const countries_data = { "BAH": { "country": "Bahrain", "adjective": "Bahrain", "circuit": "Bahrain", "track": "Bahrain International Circuit", "rlToolsName": "sakhir.2005" }, "AUS": { "country": "Australia", "adjective": "Australian", "circuit": "Albert Park", "track": "Albert Park Circuit", "rlToolsName": "albert.park.2021" }, "SAU": { "country": "Saudi Arabia", "adjective": "Saudi Arabian", "circuit": "Jeddah", "track": "Jeddah Corniche Circuit", "rlToolsName": "jeddah.2021" }, "IMO": { "country": "Imola", "adjective": "Emilia Romagna", "circuit": "Imola", "track": "Autodromo Enzo e Dino Ferrari", "rlToolsName": "imola.2008" }, "MIA": { "country": "Miami", "adjective": "Miami", "circuit": "Miami", "track": "Miami International Autodrome", "rlToolsName": "miami.2022" }, "SPA": { "country": "Spain", "adjective": "Spanish", "circuit": "Barcelona", "track": "Circuit de Barcelona-Catalunya", "rlToolsName": "barcelona.2021" }, "MON": { "country": "Monaco", "adjective": "Monaco", "circuit": "Monaco", "track": "Circuit de Monaco", "rlToolsName": "monaco.2015" }, "AZE": { "country": "Azerbaijan", "adjective": "Azerbaijan", "circuit": "Baku", "track": "Baku City Circuit", "rlToolsName": "baku.2016" }, "CAN": { "country": "Canada", "adjective": "Canadian", "circuit": "Montreal", "track": "Circuit Gilles Villeneuve", "rlToolsName": "montreal.2002" }, "GBR": { "country": "Great Britain", "adjective": "British", "circuit": "Silverstone", "track": "Silverstone Circuit", "rlToolsName": "silverstone.2011" }, "AUT": { "country": "Austria", "adjective": "Austrian", "circuit": "Red Bull Ring", "track": "Red Bull Ring", "rlToolsName": "spielberg.2016" }, "FRA": { "country": "France", "adjective": "French", "circuit": "Paul Ricard", "track": "Circuit Paul Ricard", "rlToolsName": "paulricard.2005" }, "HUN": { "country": "Hungary", "adjective": "Hungarian", "circuit": "Hungaroring", "track": "Hungaroring", "rlToolsName": "hungaroring.2003" }, "BEL": { "country": "Belgium", "adjective": "Belgian", "circuit": "Spa", "track": "Circuit de Spa-Francorchamps", "rlToolsName": "spa.2007" }, "ITA": { "country": "Italy", "adjective": "Italian", "circuit": "Monza", "track": "Autodromo Nazionale Monza", "rlToolsName": "monza.2000" }, "SGP": { "country": "Singapore", "adjective": "Singapore", "circuit": "Marina Bay", "track": "Marina Bay Street Circuit", "rlToolsName": "singapore.2023" }, "JAP": { "country": "Japan", "adjective": "Japanese", "circuit": "Suzuka", "track": "Suzuka International Racing Course", "rlToolsName": "suzuka.2003" }, "USA": { "country": "United States", "adjective": "United States", "circuit": "COTA", "track": "Circuit of the Americas", "rlToolsName": "austin.2012" }, "MEX": { "country": "Mexico", "adjective": "Mexican", "circuit": "Mexico City", "track": "Autódromo Hermanos Rodríguez", "rlToolsName": "mexico.2015" }, "BRA": { "country": "Brazil", "adjective": "Brazilian", "circuit": "Interlagos", "track": "Autódromo José Carlos Pace", "rlToolsName": "interlagos.2000" }, "UAE": { "country": "Abu Dhabi", "adjective": "Abu Dhabi", "circuit": "Yas Marina", "track": "Yas Marina Circuit", "rlToolsName": "yasmarina.2021" }, "NED": { "country": "Netherlands", "adjective": "Netherlands", "circuit": "Zandvoort", "track": "Circuit Zandvoort", "rlToolsName": "zandvoort.2020" }, "VEG": { "country": "Vegas", "adjective": "Vegas", "circuit": "Las Vegas", "track": "Las Vegas Street Circuit", "rlToolsName": "las.vegas.2023" }, "QAT": { "country": "Qatar", "adjective": "Qatar", "circuit": "Lusail", "track": "Lusail International Circuit", "rlToolsName": "losail.2023" }, "CHI": { "country": "China", "adjective": "Chinese", "circuit": "Shanghai", "track": "Shanghai International Circuit", "rlToolsName": "shanghai.2004" } }; // custom news export const CUSTOM_NEWS_TYPE_META = { race_result: { label: "Race result", titleType: 2, group: "normal" }, quali_result: { label: "Qualifying result", titleType: 1, group: "normal" }, race_reaction: { label: "Post-race reactions", titleType: 16, group: "normal" }, fake_transfer: { label: "Transfer rumor", titleType: 7, group: "normal" }, big_transfer: { label: "Big transfer confirmed", titleType: 6, group: "normal" }, massive_exit: { label: "Massive exit", titleType: 17, group: "normal" }, massive_signing: { label: "Massive signing", titleType: 18, group: "normal" }, contract_renewal: { label: "Contract renewal", titleType: 10, group: "normal" }, silly_season_rumors: { label: "Silly season rumors", titleType: 4, group: "normal" }, team_comparison: { label: "Team comparison", group: "normal" }, driver_comparison: { label: "Driver comparison", titleType: 13, group: "normal" }, season_review: { label: "Season review", group: "normal" }, potential_champion: { label: "Potential champion", titleType: 8, group: "normal" }, world_champion: { label: "World champion", titleType: 9, group: "normal" }, next_season_grid: { label: "Next season grid", titleType: 19, group: "normal" }, feeder_series_review: { label: "Feeder series review", titleType: 20, group: "normal" }, turning_point_technical_directive: { label: "Technical directive", titleType: 100, turningPoint: true, group: "turning_point" }, turning_point_transfer: { label: "Mid-season transfer", titleType: 101, turningPoint: true, group: "turning_point" }, turning_point_investment: { label: "Investment", titleType: 102, turningPoint: true, group: "turning_point" }, turning_point_dsq: { label: "DSQ", titleType: 103, turningPoint: true, group: "turning_point" }, turning_point_race_substitution: { label: "Race substitution", titleType: 105, turningPoint: true, group: "turning_point" }, turning_point_injury: { label: "Injury or illness", titleType: 106, turningPoint: true, group: "turning_point" }, turning_point_engine_regulation: { label: "Engine regulation", titleType: 107, turningPoint: true, group: "turning_point" }, turning_point_young_drivers: { label: "Young drivers", titleType: 108, turningPoint: true, group: "turning_point" }, turning_point_aduo: { label: "ADUO", titleType: 109, turningPoint: true, group: "turning_point" }, custom_new: { label: "Custom article", group: "custom" } }; export const CUSTOM_NEWS_INVESTMENT_COUNTRIES = [ "China", "Saudi Arabia", "United Arab Emirates", "India", "Russia", "South Africa", "Qatar", "Bahrain", "Singapore", "Vietnam" ]; export const CUSTOM_NEWS_DSQ_COMPONENTS = [ "engine brake map", "fuel flow", "front wing", "rear wing", "diffuser", "floor", "brake ducts", "suspension", "gearbox", "cooling system", "hydraulics", "clutch", "plank wear" ]; export const CUSTOM_NEWS_ENGINE_CHANGE_AREAS = [ "fuel flow monitoring", "ERS deployment limits", "MGU-K usage rules", "cooling system allowances", "gearbox durability limits", "turbo efficiency limits", "oil consumption rules", "hybrid system architecture", "engine architecture layout", "combustion concept rules", "turbocharger design limits", "energy recovery system redesign", "fuel system design rules", "power unit packaging regulations" ]; export const CUSTOM_NEWS_ADUO_QUARTERS = [ { value: "1", label: "1st quarter" }, { value: "2", label: "2nd quarter" }, { value: "3", label: "3rd quarter" } ]; export const CUSTOM_NEWS_IMAGE_FILES = ['1_gar.webp', '1_media.webp', '1_pad.webp', '1_shot.webp', '10_gar.webp', '10_pad.webp', '102_pad.webp', '105_pad.webp', '107_pad.webp', '11_pad.webp', '116_pad.webp', '12_pad.webp', '13_pad.webp', '14_pad.webp', '142_pad.webp', '144_pad.webp', '15_pad.webp', '17_pad.webp', '18_pad.webp', '2_gar.webp', '2_media.webp', '2_pad.webp', '2_shot.webp', '23_pad.webp', '242_pad.webp', '245_pad.webp', '255_pad.webp', '279_pad.webp', '286_pad.webp', '3_gar.webp', '3_media.webp', '3_pad.webp', '3_shot.webp', '373_pad.webp', '376_pad.webp', '4_gar.webp', '4_media.webp', '4_shot.webp', '5_gar.webp', '5_media.webp', '5_shot.webp', '6_gar.webp', '6_pad.webp', '6_shot.webp', '7_shot.webp', '77_pad.webp', '8_gar.webp', '8_shot.webp', '81_pad.webp', '83_pad.webp', '87_pad.webp', '9_gar.webp', '95_pad.webp', 'af_factory.webp', 'af1.webp', 'af2.webp', 'al_factory.webp', 'al1.webp', 'al2.webp', 'as_factory.webp', 'as1.webp', 'as2.webp', 'at_factory.webp', 'at1.webp', 'at2.webp', 'aus.webp', 'aus_car.webp', 'aus_tra.webp', 'aut.webp', 'aut_car.webp', 'aut_tra.webp', 'aze.webp', 'aze_car.webp', 'aze_tra.webp', 'bah.webp', 'bah_car.webp', 'bah_inv.webp', 'bah_tra.webp', 'bel.webp', 'bel_car.webp', 'bel_tra.webp', 'bra.webp', 'bra_car.webp', 'bra_tra.webp', 'can.webp', 'can_car.webp', 'can_tra.webp', 'champ1.webp', 'champ2.webp', 'champ3.webp', 'champ4.webp', 'champ5.webp', 'chi.webp', 'chi_car.webp', 'chi_inv.webp', 'chi_tra.webp', 'con1.webp', 'con10.webp', 'con11.webp', 'con12.webp', 'con2.webp', 'con3.webp', 'con4.webp', 'con5.webp', 'con6.webp', 'con7.webp', 'con8.webp', 'con9.webp', 'ct_factory.webp', 'dsq_1.webp', 'dsq_2.webp', 'dsq_3.webp', 'dsq_4.webp', 'dsq_5.webp', 'dsq_6.webp', 'dsq_7.webp', 'dsq_8.webp', 'engine_1.webp', 'engine_10.webp', 'engine_2.webp', 'engine_3.webp', 'engine_4.webp', 'engine_5.webp', 'engine_6.webp', 'engine_7.webp', 'engine_8.webp', 'engine_9.webp', 'fe_factory.webp', 'fe1.webp', 'fe2.webp', 'fe3.webp', 'fe4.webp', 'fe5.webp', 'gbr.webp', 'gbr_car.webp', 'gbr_tra.webp', 'grid_1.webp', 'grid_2.webp', 'grid_3.webp', 'grid_4.webp', 'ha_factory.webp', 'ha1.webp', 'ha2.webp', 'hun.webp', 'hun_car.webp', 'hun_tra.webp', 'imo.webp', 'imo_car.webp', 'imo_tra.webp', 'ind_inv.webp', 'ita.webp', 'ita_car.webp', 'ita_tra.webp', 'jap.webp', 'jap_car.webp', 'jap_tra.webp', 'mc_factory.webp', 'mc1.webp', 'mc2.webp', 'me_factory.webp', 'me1.webp', 'me2.webp', 'me3.webp', 'me4.webp', 'mex.webp', 'mex_car.webp', 'mex_tra.webp', 'mia.webp', 'mia_car.webp', 'mia_tra.webp', 'mon.webp', 'mon_car.webp', 'mon_tra.webp', 'monaco_media.webp', 'ned.webp', 'ned_car.webp', 'ned_tra.webp', 'part_3_1.webp', 'part_3_2.webp', 'part_3_3.webp', 'part_4_1.webp', 'part_4_2.webp', 'part_4_3.webp', 'part_5_1.webp', 'part_5_2.webp', 'part_5_3.webp', 'part_6_1.webp', 'part_6_2.webp', 'part_6_3.webp', 'part_7_1.webp', 'part_7_2.webp', 'part_7_3.webp', 'part_8_1.webp', 'part_8_2.webp', 'part_8_3.webp', 'qat.webp', 'qat_car.webp', 'qat_inv.webp', 'qat_tra.webp', 'rb_factory.webp', 'rb1.webp', 'rb2.webp', 'rb3.webp', 'rb4.webp', 'rus_inv.webp', 'sau.webp', 'sau_car.webp', 'sau_inv.webp', 'sau_tra.webp', 'sgp.webp', 'sgp_car.webp', 'sgp_tra.webp', 'sin_inv.webp', 'sou_inv.webp', 'spa.webp', 'spa_car.webp', 'spa_tra.webp', 'uae.webp', 'uae_car.webp', 'uae_tra.webp', 'uni_inv.webp', 'usa.webp', 'usa_car.webp', 'usa_tra.webp', 'veg.webp', 'veg_car.webp', 'veg_tra.webp', 'vie_inv.webp', 'wi_factory.webp', 'wi1.webp', 'wi2.webp', 'wi3.webp', 'young_1.webp', 'young_2.webp', 'young_3.webp', 'young_4.webp', 'young_5.webp', 'young_6.webp', 'young_7.webp', 'young_8.webp', 'young_9.webp']; export function getParamMap(data) { return { 1: { pole_driver: data.pole_driver, season_year: data.seasonYear, circuit: data.circuit, country: data.country, adjective: data.adjective }, 2: { winner: data.winnerName, season_year: data.seasonYear, circuit: data.circuit, country: data.country, adjective: data.adjective }, 4: { driver1: data.driver1, driver2: data.driver2, driver3: data.driver3, team1: data.team1, team2: data.team2, team3: data.team3 }, 6: { driver1: data.driver1, team1: data.team1, team2: data.team2, season_year: data.season_year }, 7: { driver1: data.driver1, team1: data.team1 }, 8: { driver_name: data.driver_name, circuit: data.circuit, country: data.country, adjective: data.adjective, season_year: data.season_year }, 9: { driver_name: data.driver_name, circuit: data.circuit, country: data.country, adjective: data.adjective, season_year: data.season_year }, 10: { driver1: data.driver1, team1: data.team1 }, 11: { team1: data.teamId, }, 12: { team1: data.teamId, }, 13: { driver1: data.driver1, driver2: data.driver2, team: data.team }, 14: { driver1: data.driver1, driver2: data.driver2 }, 15: { season_year: data.season, driver1: data.driver1, }, 16: { happy_driver: data.randomHappyDriver?.name, unhappy_driver: data.randomUnHappyDriver?.name, happy_team: data.happyTeam, unhappy_team: data.unhappyTeam, circuit: data.circuit, country: data.country, adjective: data.adjective }, 17: { driver1: data.driver1, team1: data.team1, season_year: data.season_year }, 18: { driver1: data.driver1, team2: data.team2 }, 19: { season_year: data.season_year }, 20: { f2_champion: data.f2_champion, f3_champion: data.f3_champion, season_year: data.season_year }, 100: { component: data.component }, 101: { team: data.team, driver_out: data.driver_out?.name ?? "", driver_in: data.driver_in?.name ?? "" }, 102: { team: data.teamName, country: data.country, amount: data.investmentAmount, share: data.investmentShare }, 103: { team: data.team, adjective: data.adjective, component: data.component, country: data.country, circuit: data.circuit }, 105: { original_race: data.originalCountry, substitute_race: data.substituteCountry, reason: data.reason }, 106: { driver: data.driver_affected?.name ?? "", team: data.team ?? "", next_race: data.condition?.races_affected?.[0]?.country ?? data.condition?.expectedReturnCountry ?? "", condition: data.condition?.condition ?? data.condition?.type ?? "", reserve_driver: data.reserve_driver?.name ?? "", reason: data.condition?.reason ?? "", races_affected_count: Array.isArray(data.condition?.races_affected) ? data.condition.races_affected.length : 0, expected_return: data.condition?.expectedReturnCountry ?? "", end_date: data.condition?.end_date ?? "" }, 107: { type: data.changeType ?? "", change_area: data.mainChangeArea ?? "" }, 108: { driver1: data.driver1, driver2: data.driver2, driver3: data.driver3 }, 109: { manufacturers: data.manufacturers, number_period: data.quarterString } }; } export const opinionDict = { "0": "Positive", "1": "Neutral", "2": "Negative" } // renderer export const difficultyConfig = { "default": { visible: ["defaultDif"], lightDif: { className: "dif-warning", text: "" }, researchDif: { className: "dif-warning", text: "" }, statDif: { className: "dif-warning", text: "" }, designTimeDif: { className: "dif-warning", text: "" }, buildDif: { className: "dif-warning", text: "" } }, "reduced weight": { visible: ["lightDif"], lightDif: { className: "dif-warning extra-hard", text: "Lightweight parts" }, }, "extra-hard": { visible: ["lightDif", "researchDif", "statDif"], lightDif: { className: "dif-warning extra-hard", text: "Lightweight parts" }, researchDif: { className: "dif-warning extra-hard", text: "Small research boost" }, statDif: { className: "dif-warning extra-hard", text: "Stats boost +0.5%" }, }, "brutal": { visible: ["lightDif", "researchDif", "statDif", "designTimeDif"], lightDif: { className: "dif-warning extra-hard", text: "Lightweight parts" }, researchDif: { className: "dif-warning brutal", text: "Moderate research boost" }, statDif: { className: "dif-warning brutal", text: "Stats boost +0.8%" }, designTimeDif: { className: "dif-warning brutal", text: "Design times reduced 5%" }, }, "unfair": { visible: ["lightDif", "researchDif", "statDif", "designTimeDif"], lightDif: { className: "dif-warning extra-hard", text: "Lightweight parts" }, researchDif: { className: "dif-warning unfair", text: "Large research boost" }, statDif: { className: "dif-warning unfair", text: "Stats boost +1.5%" }, designTimeDif: { className: "dif-warning unfair", text: "Design times reduced 11%" }, }, "insane": { visible: ["lightDif", "researchDif", "statDif", "designTimeDif", "buildDif"], lightDif: { className: "dif-warning extra-hard", text: "Lightweight parts" }, researchDif: { className: "dif-warning insane", text: "Huge research boost" }, statDif: { className: "dif-warning insane", text: "Stats boost +2%" }, designTimeDif: { className: "dif-warning insane", text: "Design times reduced 16%" }, buildDif: { className: "dif-warning insane", text: "+1 part when design completed" }, }, "impossible": { visible: ["lightDif", "researchDif", "statDif", "designTimeDif", "buildDif"], lightDif: { className: "dif-warning impossible", text: "ULTRA-lightweight parts" }, researchDif: { className: "dif-warning impossible", text: "Massive research boost" }, statDif: { className: "dif-warning impossible", text: "Stats boost +3%" }, designTimeDif: { className: "dif-warning impossible", text: "Design times reduced 20%" }, buildDif: { className: "dif-warning impossible", text: "+2 parts when design completed" }, } }; export const weightDifConfig = {0 : {text: "Disabled", className: "disabled"}, 1 : {text: "Lightweight parts", className: "extra-hard"}, 5 : {text: "ULTRA Lightweight parts", className: "impossible"}} export const defaultDifficultiesConfig = {0 : {text: "Disabled", className: "disabled"}, 1 : {text: "Extra Hard", className: "extra-hard"}, 2 : {text: "Brutal", className: "brutal"}, 3 : {text: "Unfair", className: "unfair"}, 4 : {text: "Insane", className: "insane"}, 5 : {text: "Impossible", className: "impossible"}} export const defaultTurningPointsFrequencyPreset = 2; export const turningPointsFrequencyLabels = [ "Much less", "Less", "Default", "More", "Most" ]; export const turningPointsTuningByType = { dsq: { chance: [0.02, 0.05, 0.08, 0.15, 0.25], max: [1, 1, 2, 3, 4], }, midSeasonTransfers: { chance: [0.05, 0.15, 0.25, 0.7, 0.9], max: [1, 2, 2, 3, 3], }, technicalDirective: { chance: [0.1, 0.25, 0.4, 0.6, 0.8], max: [1, 1, 2, 2, 2], }, investment: { chance: [0.02, 0.05, 0.1, 0.2, 0.35], max: [1, 1, 1, 2, 3], }, raceSubstitution: { chance: [0.02, 0.05, 0.1, 0.2, 0.35], max: [1, 1, 1, 2, 3], }, injury: { chance: [0.05, 0.12, 0.2, 0.35, 0.5], max: [1, 1, 2, 3, 3], }, engineRegulation: { chance: [0.15, 0.3, 0.5, 0.75, 0.9], max: [1, 1, 1, 1, 1], }, youngDrivers: { chance: [0.25, 0.5, 1, 1, 1], max: [1, 1, 1, 1, 1], }, }; export const defaultColors = { 1: 4293394477, 2: 4294934528, 3: 4281758150, 4: 4280808658, 5: 4278227916, 6: 4284794111, 7: 4290165437, 8: 4284912383, 9: 4283621970, 10: 4280457585 }; export const customColors = { "audi": "#C00A26", "sauber": "#f50537", "stake": "#54d650", "visarb": "#6c8ff3", "toyota": "#989898", "brawn": "#d0e610", "porsche": "#873AC4", "alphatauri": "#5E8FAA", "hugo": "#bd9514", "lotus": "#b09247", "renault": "#b09247", "andretti": "#fac51c", "alpine": "#F168BA", "racingpoint": "#F395C7", "jordan": "#e6e624", "bmw": "#f1f1f1", "cadillac": "#111111", "ford": "#3172bf" } ================================================ FILE: src/js/frontend/devTools.js ================================================ import { saveAs } from "file-saver"; import { Command } from "../backend/command.js"; const isLocalhost = window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1"; if (isLocalhost) { let windowEl = null; let isOpen = false; let isDragging = false; let dragOffsetX = 0; let dragOffsetY = 0; function createWindow() { windowEl = document.createElement("div"); windowEl.className = "devtools-window"; windowEl.setAttribute("role", "dialog"); windowEl.setAttribute("aria-modal", "false"); const headerEl = document.createElement("div"); headerEl.className = "devtools-header"; const chromeEl = document.createElement("div"); chromeEl.className = "devtools-chrome"; const chromeDotRed = document.createElement("span"); chromeDotRed.className = "devtools-dot is-red"; const chromeDotYellow = document.createElement("span"); chromeDotYellow.className = "devtools-dot is-yellow"; const chromeDotGreen = document.createElement("span"); chromeDotGreen.className = "devtools-dot is-green"; const titleEl = document.createElement("div"); titleEl.className = "devtools-title"; titleEl.textContent = "DEVTOOLS.EXE"; const statusEl = document.createElement("div"); statusEl.className = "devtools-status"; statusEl.textContent = "LOCALHOST SESSION"; const closeButton = document.createElement("button"); closeButton.className = "devtools-close"; closeButton.type = "button"; closeButton.setAttribute("aria-label", "Close"); closeButton.textContent = "\u00D7"; const bodyEl = document.createElement("div"); bodyEl.className = "devtools-body"; const hintEl = document.createElement("div"); hintEl.className = "devtools-hint"; hintEl.textContent = "Ctrl+D to toggle"; const statsButton = document.createElement("button"); statsButton.className = "devtools-action"; statsButton.type = "button"; statsButton.textContent = "Set all drivers stats to 85"; const downloadButton = document.createElement("button"); downloadButton.className = "devtools-action"; downloadButton.type = "button"; downloadButton.textContent = "Download database (.db)"; chromeEl.appendChild(chromeDotRed); chromeEl.appendChild(chromeDotYellow); chromeEl.appendChild(chromeDotGreen); headerEl.appendChild(chromeEl); headerEl.appendChild(titleEl); headerEl.appendChild(statusEl); headerEl.appendChild(closeButton); bodyEl.appendChild(hintEl); bodyEl.appendChild(statsButton); bodyEl.appendChild(downloadButton); windowEl.appendChild(headerEl); windowEl.appendChild(bodyEl); document.body.appendChild(windowEl); closeButton.addEventListener("click", () => { closeWindow(); }); closeButton.addEventListener("pointerdown", (e) => { e.stopPropagation(); }); statsButton.addEventListener("click", () => { statsButton.disabled = true; statsButton.textContent = "Working..."; new Command("devSetAllDriversStats85", {}).execute(); setTimeout(() => { statsButton.disabled = false; statsButton.textContent = "Set all drivers stats to 85"; }, 1200); }); downloadButton.addEventListener("click", async () => { downloadButton.disabled = true; downloadButton.textContent = "Downloading..."; try { const response = await new Command("devDownloadDatabase", {}).promiseExecute(); const filename = response?.content?.filename ?? "database.db"; const fileData = response?.content?.fileData; if (!fileData) { throw new Error("Missing database data"); } saveAs(new Blob([fileData], { type: "application/vnd.sqlite3" }), filename); downloadButton.textContent = "Downloaded"; } catch (error) { console.error(error); downloadButton.textContent = "Download failed"; } setTimeout(() => { downloadButton.disabled = false; downloadButton.textContent = "Download database (.db)"; }, 1200); }); headerEl.addEventListener("pointerdown", (e) => { if (e.button !== 0) return; if (e.target.closest(".devtools-close")) return; isDragging = true; const rect = windowEl.getBoundingClientRect(); dragOffsetX = e.clientX - rect.left; dragOffsetY = e.clientY - rect.top; windowEl.style.left = `${rect.left}px`; windowEl.style.top = `${rect.top}px`; windowEl.style.transform = "none"; windowEl.classList.add("dragging"); headerEl.setPointerCapture(e.pointerId); e.preventDefault(); }); headerEl.addEventListener("pointermove", (e) => { if (!isDragging) return; const x = e.clientX - dragOffsetX; const y = e.clientY - dragOffsetY; windowEl.style.left = `${x}px`; windowEl.style.top = `${y}px`; }); headerEl.addEventListener("pointerup", (e) => { if (!isDragging) return; isDragging = false; windowEl.classList.remove("dragging"); try { headerEl.releasePointerCapture(e.pointerId); } catch (err) {} }); } function openWindow() { if (!windowEl) createWindow(); windowEl.classList.add("show"); isOpen = true; } function closeWindow() { if (!windowEl) return; windowEl.classList.remove("show"); isOpen = false; } function toggleWindow() { if (isOpen) closeWindow(); else openWindow(); } window.addEventListener( "keydown", (e) => { if (e.repeat) return; if (!e.ctrlKey) return; if (e.altKey || e.shiftKey) return; if (e.code !== "KeyD") return; e.preventDefault(); toggleWindow(); }, true ); window.addEventListener("keydown", (e) => { if (e.key === "Escape") closeWindow(); }); } ================================================ FILE: src/js/frontend/dragFile.js ================================================ // dragDrop.js import { gamePill, editorPill, setSaveName, new_update_notifications, setIsShowingNotification } from "./renderer.js"; import { saveHandleToRecents, getRecentHandles } from "./recentsManager.js"; import { Command } from "../backend/command.js"; let carAnalysisUtils = null; export const dbWorker = new Worker(new URL('../backend/worker.js', import.meta.url)); const dropDiv = document.querySelector(".drop-div"); const statusCircle = document.getElementById("statusCircle"); const statusIcon = document.getElementById("statusIcon"); const statusTitle = document.getElementById("statusTitle"); const loadingSpinner = document.querySelector(".loading-spinner"); const statusDesc = document.getElementById("statusDesc"); const body = document.querySelector("body"); export const handleDragEnter = (event) => { event.preventDefault(); body.classList.add("drag-active"); }; export const handleDragOver = (event) => { event.preventDefault(); event.dataTransfer.dropEffect = "copy"; }; export const handleDragLeave = (event) => { event.preventDefault(); body.classList.remove("drag-active"); }; export const handleDrop = async (event) => { event.preventDefault(); body.classList.remove("drag-active"); const item = event.dataTransfer.items[0]; // ... el resto de tu lógica de drop ... if (item && item.kind === 'file') { try { const handle = await item.getAsFileSystemHandle(); if (handle) { await saveHandleToRecents(handle); const file = await handle.getFile(); await processSaveFile(file) } else { const file = item.getAsFile(); await processSaveFile(file); } } catch (e) { console.error("Error with file handle:", e); const file = event.dataTransfer.files[0]; await processSaveFile(file); } } }; dropDiv.addEventListener("dragenter", handleDragEnter); dropDiv.addEventListener("dragover", handleDragOver); dropDiv.addEventListener("dragleave", handleDragLeave); dropDiv.addEventListener("drop", handleDrop); const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms)); export async function processSaveFile(file) { if (!file) return; // --- Validaciones de archivo --- if (file.name.split('.').pop() === "vdf") { console.error("File not supported"); new_update_notifications( 'File type not supported. See this video to find your save file.', "error" ); return; } else if (file.name.split('.').pop() === "sav") { const footerNotification = document.querySelector('.footer-notification'); if (footerNotification && footerNotification.classList.contains('error')) { footerNotification.classList.remove('show'); setIsShowingNotification(false); } setSaveName(file.name); // 1. Ponemos el icono en modo SPINNER await updateStatusUI('loading'); // 2. Definimos la tarea de carga const dbLoadTask = new Promise((resolve, reject) => { dbWorker.postMessage({ command: 'loadDB', data: { file: file } }); dbWorker.onmessage = (msg) => { if (msg.data.responseMessage === "Database loaded") { console.log("[Main Thread] Database loaded in Worker"); const dateObj = new Date(msg.data.content); const day = dateObj.getDate(); const month = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(dateObj); const year = dateObj.getFullYear(); const completeDay = day + (day % 10 == 1 && day != 11 ? "st" : day % 10 == 2 && day != 12 ? "nd" : day % 10 == 3 && day != 13 ? "rd" : "th"); document.querySelector("#dateDay").textContent = completeDay; document.querySelector("#dateMonth").textContent = month; document.querySelector("#dateYear").textContent = year; document.querySelector("#dateDay2026").textContent = completeDay; document.querySelector("#dateMonth2026").textContent = month; document.querySelector("#dateYear2026").textContent = year; resolve(); } else if (msg.data.error) { console.error("[Main Thread] Error loading DB:", msg.data.error); reject(new Error(msg.data.error)); } }; }); // 3. Ejecutamos: Carga + Espera try { await Promise.all([ dbLoadTask, wait(2000) ]); // 4. Ponemos el icono en modo CHECK VERDE await updateStatusUI('success', { filename: file.name }); // 5. Esperamos 1 segundo extra await wait(1000); // 6. Finalmente mostramos el editor editorPill.classList.remove("d-none"); gamePill.classList.remove("d-none"); patreonPill.classList.remove("d-none"); const command = new Command("saveSelected", {}); command.execute(); document.querySelector(".script-selector").classList.remove("hidden"); document.querySelector(".footer").classList.remove("hidden"); } catch (error) { console.error("Error en el proceso:", error); // Aquí podrías manejar el error visualmente si quisieras } } } async function updateStatusUI(type, textConfig) { statusIcon.classList.add("icon-scale-0"); await wait(170); if (type === 'loading') { loadingSpinner.classList.add("show"); statusCircle.classList.remove("success-mode"); statusTitle.textContent = "Analyzing database..."; statusDesc.innerText = "This may take a few seconds."; } else if (type === 'success') { loadingSpinner.classList.remove("show"); // Cambiar icono a Check y Colores statusIcon.className = "bi bi-check-lg"; // Volvemos a poner clase de icono statusIcon.classList.add("success-mode"); // Color verde al icono statusCircle.classList.add("success-mode"); // Fondo verde al circulo // Textos statusTitle.textContent = "Save loaded successfully!"; statusDesc.innerText = textConfig.filename; await wait(50); statusIcon.classList.remove("icon-scale-0"); } } ================================================ FILE: src/js/frontend/head2head.js ================================================ import { races_names, team_dict, combined_dict, lightColors, theme_colors, logos_disc } from "./config"; import { game_version, custom_team, selectedTheme } from "./renderer"; import { insert_space, manageColor, format_name } from "./transfers"; import Chart from 'chart.js/auto'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import annotationPlugin from 'chartjs-plugin-annotation'; import { Command } from "../backend/command.js";; let driver1_selected = false; let driver2_selected = false; let driver1Sel; let driver2Sel; let pos_dict = { 1: "1st", 2: "2nd", 3: "3rd" } let d1_team let d2_team let wins = false; let poles = false; let sprints = false; let race = 0; let quali = 0; const raceMenuLength = 7; const qualiMenuLength = 6; let driverGraph; let pointsGraph; let qualiGraph; let gapWinnerGraph; let gapPoleGraph; let compData; let annotationsToggle = true; let h2hCount = 0; let graphCount = 0; let h2hList = [] let graphList = [] let h2hTeamList = [] let graphTeamList = [] let mode = "driver" let h2hData; let queuedAutoCompareDrivers = null; export let mid_grid = 10; export let max_races = 23; export let relative_grid = 5; function formatSignedValue(value) { if (value > 0) { return `+${value}`; } return `${value}`; } function getCompletedRaceIdsSet(data) { const completedRaceIds = data?.[data.length - 1]; if (!Array.isArray(completedRaceIds)) { return new Set(); } return new Set(completedRaceIds.map((raceId) => Number(raceId))); } function renderTeamLogo(container, teamId) { if (!container) return; const logoSrc = logos_disc[teamId]; const teamName = combined_dict[teamId] || ""; container.innerHTML = ""; container.parentNode.querySelector(".team-h2h-name-bg")?.remove(); container.title = teamName; if (!logoSrc) { container.textContent = teamName; return; } const watermark = document.createElement("span"); watermark.className = "team-h2h-name-bg bold-font"; watermark.textContent = teamName.toUpperCase(); container.parentNode.appendChild(watermark); const logoImg = document.createElement("img"); logoImg.className = "drivers-table-logo team-h2h-logo"; logoImg.src = logoSrc; logoImg.alt = teamName; container.appendChild(logoImg); } export function setMidGrid(value) { mid_grid = value } export function setMaxRaces(value) { max_races = value } export function setRelativeGrid(value) { relative_grid = value } export let colors_dict; //changed as the ct colors changes, so it stays Chart.register(ChartDataLabels); Chart.register(annotationPlugin); export function init_colors_dict(theme = "default-theme") { console.log("Selected theme:", theme); colors_dict = { "10": "#F91536", "11": theme_colors[theme].general_secondary, "20": "#F58020", "21": "#47c7fc", "30": "#3671C6", "31": "#ffd300", "40": "#6CD3BF", "41": theme_colors[theme].general_secondary, "50": "#F168BA", "51": theme_colors[theme].general_secondary, "60": "#1868DB", "61": theme_colors[theme].general_secondary, "70": "#B6BABD", "71": "#f62039", "80": "#5E8FAA", "81": theme_colors[theme].general_secondary, "90": "#C92D4B", "91": theme_colors[theme].general_secondary, "100": "#358C75", "101": "#c3dc00", "320": "#ffffff", "321": "#000000" } } export function get_colors_dict() { return colors_dict; } export function edit_colors_dict(key, value) { colors_dict[key] = value; } /** * Puts the bars of the head to head with the correct width for the drivers selected * @param {object} data object with all the info of the comparision between both drivers */ export function manage_h2h_bars(data) { let relValue let d1_width let d2_width compData = data if (data[7].some(elem => elem >= 2)) { data[4] = data[7] document.getElementById("bestrh2h").querySelector(".only-name").textContent = "WINS" wins = true } else { document.getElementById("bestrh2h").querySelector(".only-name").textContent = "BEST RACE" wins = false } if (data[8].some(elem => elem >= 2)) { data[5] = data[8] document.getElementById("bestqh2h").querySelector(".only-name").textContent = "POLES" poles = true } else { document.getElementById("bestqh2h").querySelector(".only-name").textContent = "BEST QUALI" poles = false } if (data[9].some(elem => elem >= 1)) { document.getElementById("bestrh2h").querySelector(".name-H2H").style.justifyContent = "space-between" document.getElementById("bestrh2h").querySelectorAll("i").forEach(function (elem) { elem.classList.remove("d-none") }) } else { document.getElementById("bestrh2h").querySelector(".name-H2H").style.justifyContent = "center" document.getElementById("bestrh2h").querySelectorAll("i").forEach(function (elem) { elem.classList.add("d-none") }) } document.querySelectorAll(".one-statH2H").forEach(function (elem, index) { if (elem.id === "bestrh2h" || elem.id === "bestqh2h") { if (!wins && elem.id === "bestrh2h") { d1_width = 100 - (data[index][0] - 1) * relative_grid d2_width = 100 - (data[index][1] - 1) * relative_grid if (data[index][0] <= 3) { elem.querySelector(".driver1-number").textContent = pos_dict[data[index][0]] } else { elem.querySelector(".driver1-number").textContent = data[index][0] + "th" } if (data[index][1] <= 3) { elem.querySelector(".driver2-number").textContent = pos_dict[data[index][1]] } else { elem.querySelector(".driver2-number").textContent = data[index][1] + "th" } } else if (wins && elem.id === "bestrh2h") { relValue = (100 / (data[index][0] + data[index][1])).toFixed(2) d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] } if (!poles && elem.id === "bestqh2h") { d1_width = 100 - (data[index][0] - 1) * relative_grid d2_width = 100 - (data[index][1] - 1) * relative_grid if (data[index][0] <= 3) { elem.querySelector(".driver1-number").textContent = pos_dict[data[index][0]] } else { elem.querySelector(".driver1-number").textContent = data[index][0] + "th" } if (data[index][1] <= 3) { elem.querySelector(".driver2-number").textContent = pos_dict[data[index][1]] } else { elem.querySelector(".driver2-number").textContent = data[index][1] + "th" } } else if (poles && elem.id === "bestqh2h") { relValue = (100 / (data[index][0] + data[index][1])).toFixed(2) d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] } } else { if (elem.id === "raceh2h" || elem.id === "qualih2h") { let index2 if (elem.id === "raceh2h") { index2 = 10 } else { index2 = 11 } let d1Num = elem.querySelector(".avg-comparison").querySelector(".driver1-avg") d1Num.className = "driver1-avg bold-font" let d2Num = elem.querySelector(".avg-comparison").querySelector(".driver2-avg") d2Num.className = "driver2-avg bold-font" let d1 = compData[index2][0]; if (compData[index2][0] > 0) { d1 = "+" + compData[index2][0] d1Num.classList.add("negative") d2Num.classList.add("positive") } d1Num.innerText = d1 let d2 = compData[index2][1]; if (compData[index2][1] > 0) { d2 = "+" + compData[index2][1] d1Num.classList.add("positive") d2Num.classList.add("negative") } d2Num.innerText = d2 if (elem.id === "qualih2h") { relValue = (100 / (data[0][0] + data[0][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] if (quali === 2) { d1_width = 100 - (data[14][0] - 1) * relative_grid d2_width = 100 - (data[14][1] - 1) * relative_grid elem.querySelector(".driver1-number").textContent = data[14][0] elem.querySelector(".driver2-number").textContent = data[14][1] } else if (quali === 3) { d1_width = 100 - (data[15][0] - 1) * relative_grid d2_width = 100 - (data[15][1] - 1) * relative_grid elem.querySelector(".driver1-number").textContent = data[15][0] elem.querySelector(".driver2-number").textContent = data[15][1] } else if (quali === 4) { relValue = (100 / (data[19][0] + data[19][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[19][0] * relValue d2_width = data[19][1] * relValue elem.querySelector(".driver1-number").textContent = data[19][0] elem.querySelector(".driver2-number").textContent = data[19][1] } else if (quali === 5) { relValue = (100 / (data[20][0] + data[20][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[20][0] * relValue d2_width = data[20][1] * relValue elem.querySelector(".driver1-number").textContent = data[20][0] elem.querySelector(".driver2-number").textContent = data[20][1] } } if (elem.id === "raceh2h") { relValue = (100 / (data[0][0] + data[0][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] if (race === 2) { d1_width = 100 - (data[12][0] - 1) * relative_grid d2_width = 100 - (data[12][1] - 1) * relative_grid elem.querySelector(".driver1-number").textContent = data[12][0] elem.querySelector(".driver2-number").textContent = data[12][1] } else if (race === 3) { d1_width = 100 - (data[13][0] - 1) * relative_grid d2_width = 100 - (data[13][1] - 1) * relative_grid elem.querySelector(".driver1-number").textContent = data[13][0] elem.querySelector(".driver2-number").textContent = data[13][1] } else if (race === 4) { d1_width = 100 - (data[16][0] - 1) * relative_grid d2_width = 100 - (data[16][1] - 1) * relative_grid elem.querySelector(".driver1-number").textContent = data[16][0] elem.querySelector(".driver2-number").textContent = data[16][1] } else if (race === 5) { let maxGain = Math.max(Math.abs(data[17][0]), Math.abs(data[17][1])) if (maxGain === 0) { maxGain = 1 } d1_width = (Math.abs(data[17][0]) / maxGain) * 100 d2_width = (Math.abs(data[17][1]) / maxGain) * 100 elem.querySelector(".driver1-number").textContent = formatSignedValue(data[17][0]) elem.querySelector(".driver2-number").textContent = formatSignedValue(data[17][1]) } else if (race === 6) { relValue = (100 / (data[18][0] + data[18][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[18][0] * relValue d2_width = data[18][1] * relValue elem.querySelector(".driver1-number").textContent = data[18][0] elem.querySelector(".driver2-number").textContent = data[18][1] } } } else if (elem.id === "ptsh2h") { relValue = 100 / Math.max(data[index][0], data[index][1]) if (relValue == Infinity) { relValue = 0 } d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] } else if (elem.id === "dnfh2h" || elem.id === "podiumsh2h") { relValue = (100 / (data[index][0] + data[index][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = data[index][0] * relValue d2_width = data[index][1] * relValue elem.querySelector(".driver1-number").textContent = data[index][0] elem.querySelector(".driver2-number").textContent = data[index][1] } } if (d1_width > 100) { d1_width = 100 } if (d2_width > 100) { d2_width = 100 } fill_bars(elem, d1_width, d2_width) }) } /** * Fills the bars for the elem with driver1 and 2 data * @param {div} elem general bar for the comparision * @param {Number} d1_width driver 1's width for his bar * @param {Number} d2_width driver 2's width for his bar */ function fill_bars(elem, d1_width, d2_width) { elem.querySelector(".driver1-bar").className = "driver1-bar" elem.querySelector(".driver2-bar").className = "driver2-bar" document.querySelector(".driver1-name").className = "driver1-name" document.querySelector(".driver2-name").className = "driver2-name" elem.querySelector(".driver1-bar").classList.add(team_dict[h2hTeamList[0]] + "bar-primary") document.querySelector(".driver1-name").classList.add(team_dict[h2hTeamList[0]] + "-back-normal") if (h2hTeamList[0] === h2hTeamList[1]) { elem.querySelector(".driver2-bar").classList.add(team_dict[h2hTeamList[1]] + "bar-secondary") document.querySelector(".driver2-name").classList.add(team_dict[h2hTeamList[1]] + "-back-normal-secondary") } else { elem.querySelector(".driver2-bar").classList.add(team_dict[h2hTeamList[1]] + "bar-primary") document.querySelector(".driver2-name").classList.add(team_dict[h2hTeamList[1]] + "-back-normal") } elem.querySelector(".driver1-bar").style.width = d1_width + "%" elem.querySelector(".driver2-bar").style.width = d2_width + "%" } /** * Toggles the sprint wins comparision */ function toggle_sprints() { let elem = document.querySelector("#bestrh2h") let d1_width; let d2_width; let relValue; if (sprints) { elem.querySelector(".only-name").textContent = "SPRINT WINS" relValue = (100 / (compData[9][0] + compData[9][1])).toFixed(2) d1_width = compData[9][0] * relValue d2_width = compData[9][1] * relValue elem.querySelector(".driver1-number").textContent = compData[9][0] elem.querySelector(".driver2-number").textContent = compData[9][1] } else { if (wins) { elem.querySelector(".only-name").textContent = "WINS" relValue = (100 / (compData[4][0] + compData[4][1])).toFixed(2) d1_width = compData[4][0] * relValue d2_width = compData[4][1] * relValue elem.querySelector(".driver1-number").textContent = compData[4][0] elem.querySelector(".driver2-number").textContent = compData[4][1] } else { elem.querySelector(".only-name").textContent = "BEST RACE" d1_width = 100 - (compData[4][0] - 1) * 5 d2_width = 100 - (compData[4][1] - 1) * 5 if (compData[4][0] <= 3) { elem.querySelector(".driver1-number").textContent = pos_dict[compData[4][0]] } else { elem.querySelector(".driver1-number").textContent = compData[4][0] + "th" } if (compData[4][1] <= 3) { elem.querySelector(".driver2-number").textContent = pos_dict[compData[4][1]] } else { elem.querySelector(".driver2-number").textContent = compData[4][1] + "th" } } } fill_bars(elem, d1_width, d2_width) } function toggle_racePace() { let d1_width; let d2_width; let relValue let elem = document.querySelector("#raceh2h") if (race === 1) { elem.querySelector(".only-name").textContent = "AVG PACE DIFF (s)" elem.querySelector(".bar-space").classList.add("d-none") elem.querySelector(".avg-comparison").classList.remove("d-none") let d1Num = elem.querySelector(".avg-comparison").querySelector(".driver1-avg") d1Num.className = "driver1-avg bold-font" let d2Num = elem.querySelector(".avg-comparison").querySelector(".driver2-avg") d2Num.className = "driver2-avg bold-font" let d1 = compData[10][0]; if (compData[10][0] > 0) { d1 = "+" + compData[10][0] d1Num.classList.add("negative") d2Num.classList.add("positive") } d1Num.innerText = d1 let d2 = compData[10][1]; if (compData[10][1] > 0) { d2 = "+" + compData[10][1] d1Num.classList.add("positive") d2Num.classList.add("negative") } d2Num.innerText = d2 } else { elem.querySelector(".bar-space").classList.remove("d-none") elem.querySelector(".avg-comparison").classList.add("d-none") if (race === 0) { elem.querySelector(".only-name").textContent = "RACE" relValue = (100 / (compData[0][0] + compData[0][1])).toFixed(2) d1_width = compData[0][0] * relValue d2_width = compData[0][1] * relValue elem.querySelector(".driver1-number").textContent = compData[0][0] elem.querySelector(".driver2-number").textContent = compData[0][1] } else if (race === 2) { elem.querySelector(".only-name").textContent = "AVG RACE" d1_width = 100 - (compData[12][0] - 1) * 5 d2_width = 100 - (compData[12][1] - 1) * 5 elem.querySelector(".driver1-number").textContent = compData[12][0] elem.querySelector(".driver2-number").textContent = compData[12][1] } else if (race === 3) { elem.querySelector(".only-name").textContent = "MEDIAN RACE" d1_width = 100 - (compData[13][0] - 1) * 5 d2_width = 100 - (compData[13][1] - 1) * 5 elem.querySelector(".driver1-number").textContent = compData[13][0] elem.querySelector(".driver2-number").textContent = compData[13][1] } else if (race === 4) { elem.querySelector(".only-name").textContent = "AVG GRID" d1_width = 100 - (compData[16][0] - 1) * 5 d2_width = 100 - (compData[16][1] - 1) * 5 elem.querySelector(".driver1-number").textContent = compData[16][0] elem.querySelector(".driver2-number").textContent = compData[16][1] } else if (race === 5) { elem.querySelector(".only-name").textContent = "AVG POS GAIN" let maxGain = Math.max(Math.abs(compData[17][0]), Math.abs(compData[17][1])) if (maxGain === 0) { maxGain = 1 } d1_width = (Math.abs(compData[17][0]) / maxGain) * 100 d2_width = (Math.abs(compData[17][1]) / maxGain) * 100 elem.querySelector(".driver1-number").textContent = formatSignedValue(compData[17][0]) elem.querySelector(".driver2-number").textContent = formatSignedValue(compData[17][1]) } else if (race === 6) { elem.querySelector(".only-name").textContent = "TOP 10s" relValue = (100 / (compData[18][0] + compData[18][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = compData[18][0] * relValue d2_width = compData[18][1] * relValue elem.querySelector(".driver1-number").textContent = compData[18][0] elem.querySelector(".driver2-number").textContent = compData[18][1] } fill_bars(elem, d1_width, d2_width) } } function toggle_qualiPace() { let elem = document.querySelector("#qualih2h") let d1_width; let d2_width; let relValue; if (quali === 1) { elem.querySelector(".only-name").textContent = "AVG QUALI DIFF (s)" elem.querySelector(".bar-space").classList.add("d-none") elem.querySelector(".avg-comparison").classList.remove("d-none") let d1Num = elem.querySelector(".avg-comparison").querySelector(".driver1-avg") d1Num.className = "driver1-avg bold-font" let d2Num = elem.querySelector(".avg-comparison").querySelector(".driver2-avg") d2Num.className = "driver2-avg bold-font" let d1 = compData[11][0]; if (compData[11][0] > 0) { d1 = "+" + compData[11][0] d1Num.classList.add("negative") d2Num.classList.add("positive") } d1Num.innerText = d1 let d2 = compData[11][1]; if (compData[11][1] > 0) { d2 = "+" + compData[11][1] d1Num.classList.add("positive") d2Num.classList.add("negative") } d2Num.innerText = d2 } else { elem.querySelector(".bar-space").classList.remove("d-none") elem.querySelector(".avg-comparison").classList.add("d-none") if (quali === 0) { elem.querySelector(".only-name").textContent = "QUALIFYING" relValue = (100 / (compData[0][0] + compData[0][1])).toFixed(2) d1_width = compData[1][0] * relValue d2_width = compData[1][1] * relValue elem.querySelector(".driver1-number").textContent = compData[1][0] elem.querySelector(".driver2-number").textContent = compData[1][1] } else if (quali === 2) { elem.querySelector(".only-name").textContent = "AVG QUALI" d1_width = 100 - (compData[14][0] - 1) * 5 d2_width = 100 - (compData[14][1] - 1) * 5 elem.querySelector(".driver1-number").textContent = compData[14][0] elem.querySelector(".driver2-number").textContent = compData[14][1] } else if (quali === 3) { elem.querySelector(".only-name").textContent = "MEDIAN QUALI" d1_width = 100 - (compData[15][0] - 1) * 5 d2_width = 100 - (compData[15][1] - 1) * 5 elem.querySelector(".driver1-number").textContent = compData[15][0] elem.querySelector(".driver2-number").textContent = compData[15][1] } else if (quali === 4) { elem.querySelector(".only-name").textContent = "Q3 APPEARANCES" relValue = (100 / (compData[19][0] + compData[19][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = compData[19][0] * relValue d2_width = compData[19][1] * relValue elem.querySelector(".driver1-number").textContent = compData[19][0] elem.querySelector(".driver2-number").textContent = compData[19][1] } else if (quali === 5) { elem.querySelector(".only-name").textContent = "FRONT ROWS" relValue = (100 / (compData[20][0] + compData[20][1])).toFixed(2) if (relValue == Infinity) { relValue = 0 } d1_width = compData[20][0] * relValue d2_width = compData[20][1] * relValue elem.querySelector(".driver1-number").textContent = compData[20][0] elem.querySelector(".driver2-number").textContent = compData[20][1] } fill_bars(elem, d1_width, d2_width) } } /** * Adds listeners for the arrows to change between sprints and races */ export function sprintsListeners() { document.querySelector("#bestrh2h").querySelectorAll("i").forEach(function (elem) { elem.removeEventListener('evento2', change_sprintView); elem.addEventListener("click", change_sprintView) }) } /** * listeners to the race head to head comparison */ export function racePaceListener() { document.querySelector("#raceh2h").querySelectorAll(".bi-chevron-right").forEach(function (elem) { elem.removeEventListener('evento5', increase_racePaceView); elem.addEventListener("click", increase_racePaceView) }) document.querySelector("#raceh2h").querySelectorAll(".bi-chevron-left").forEach(function (elem) { elem.removeEventListener('evento6', decrease_racePaceView); elem.addEventListener("click", decrease_racePaceView) }) } /** * listeners to the qualifying head to head comparison */ export function qualiPaceListener() { document.querySelector("#qualih2h").querySelectorAll(".bi-chevron-right").forEach(function (elem) { elem.removeEventListener('evento3', increase_qualiPaceView); elem.addEventListener("click", increase_qualiPaceView) }) document.querySelector("#qualih2h").querySelectorAll(".bi-chevron-left").forEach(function (elem) { elem.removeEventListener('evento4', decrease_qualiPaceView); elem.addEventListener("click", decrease_qualiPaceView) }) } /** * increases the race comparison showed */ function increase_racePaceView() { race += 1 race = race % raceMenuLength toggle_racePace() } /** * decreases the race comparison showed */ function decrease_racePaceView() { race -= 1 race = (race + raceMenuLength) % raceMenuLength toggle_racePace() } /** * increases the quali comparison showed */ function increase_qualiPaceView() { quali += 1 quali = quali % qualiMenuLength toggle_qualiPace() } /** * decreases the quali comparison showed */ function decrease_qualiPaceView() { quali -= 1 quali = (quali + qualiMenuLength) % qualiMenuLength toggle_qualiPace() } /** * Changes the sprint view */ function change_sprintView() { sprints = !sprints toggle_sprints() } /** * Event listener for the annotatiosn switch */ document.getElementById("annotationsToggle").addEventListener("click", function () { annotationsToggle = !annotationsToggle if (typeof driverGraph !== 'undefined' && driverGraph !== null) { driverGraph.options.plugins.annotation.annotations.line1.display = annotationsToggle driverGraph.options.plugins.annotation.annotations.line2.display = annotationsToggle driverGraph.options.plugins.annotation.annotations.line3.display = annotationsToggle driverGraph.options.plugins.annotation.annotations.line4.display = annotationsToggle driverGraph.update(); } if (typeof qualiGraph !== 'undefined' && qualiGraph !== null) { qualiGraph.options.plugins.annotation.annotations.line1.display = annotationsToggle qualiGraph.options.plugins.annotation.annotations.line2.display = annotationsToggle qualiGraph.update(); } }) /** * hides the comparison */ export function hideComp() { document.querySelector(".drivers-modal-zone").innerHTML = "" document.querySelector("#mainH2h").classList.add("d-none") document.querySelectorAll(".modal-team").forEach(function (elem) { elem.classList.add("d-none") }) } export function queueAutoCompareDrivers(driver1Id, driver2Id) { const d1 = String(driver1Id); const d2 = String(driver2Id); if (!d1 || !d2 || d1 === "undefined" || d2 === "undefined" || d1 === d2) return; queuedAutoCompareDrivers = { d1, d2 }; } function applyQueuedAutoCompareDrivers() { if (!queuedAutoCompareDrivers) return; if (mode !== "driver") return; const zone = document.querySelector(".drivers-modal-zone"); if (!zone) return; const { d1, d2 } = queuedAutoCompareDrivers; const d1El = zone.querySelector(`.modal-driver[data-driverid="${d1}"]`); const d2El = zone.querySelector(`.modal-driver[data-driverid="${d2}"]`); if (!d1El || !d2El) return; queuedAutoCompareDrivers = null; const selectDriver = (el) => { const h2hBtn = el.querySelector(".H2Hradio"); if (h2hBtn && h2hBtn.dataset.state !== "checked") h2hBtn.click(); const graphBtn = el.querySelector(".GraphButton"); if (graphBtn && graphBtn.dataset.state !== "checked") graphBtn.click(); }; selectDriver(d1El); selectDriver(d2El); const confirm = document.getElementById("confirmComparison"); if (confirm) confirm.click(); } /** * Loads all the drivers into the menus of driver selection * @param {Object} drivers object with all the driver info */ export function load_drivers_h2h(drivers) { let dest = document.querySelector(".drivers-modal-zone") h2hCount = 0; h2hList = [] graphList = [] h2hTeamList = [] graphTeamList = [] dest.innerHTML = "" drivers.forEach(function (driver) { let newDiv = document.createElement("div"); newDiv.className = "col modal-driver"; newDiv.dataset.driverid = driver[1]; newDiv.dataset.teamid = driver[2]; let name = driver[0].split(" ") let spanName = document.createElement("span") let spanLastName = document.createElement("span") spanLastName.dataset.teamid = driver[2]; newDiv.dataset.teamid = driver[2]; newDiv.classList.add(team_dict[driver[2]] + "-transparent") format_name(driver[0], name, spanName, spanLastName) spanLastName.classList.add("bold-font") let h2hBut = document.createElement("div") h2hBut.dataset.driverid = driver[1] h2hBut.dataset.teamid = driver[2] let h2hLabel = document.createElement("div") h2hLabel.innerText = "H2H" h2hLabel.className = "no-pointer pos-relative" h2hBut.appendChild(h2hLabel) h2hBut.className = "H2Hradio" h2hBut.dataset.state = "unchecked" h2hBut.addEventListener("click", function () { if (h2hBut.dataset.state === "unchecked" && h2hCount < 2) { h2hBut.dataset.state = "checked" h2hBut.classList.add("activated") h2hCount += 1 h2hList.push(h2hBut.dataset.driverid) h2hTeamList.push(h2hBut.dataset.teamid) } else if (h2hBut.dataset.state === "checked") { h2hBut.dataset.state = "unchecked" h2hBut.classList.remove("activated") h2hCount -= 1 let ind = h2hList.indexOf(h2hBut.dataset.driverid) h2hTeamList.splice(ind, 1) h2hList = h2hList.filter(x => x !== h2hBut.dataset.driverid) } let text = document.querySelector(".H2H-text").querySelector(".text-normal") text.innerText = "- " + h2hCount + "/2 drivers selected" text.classList.add("h2h-highlight"); setTimeout(function () { text.classList.remove("h2h-highlight"); }, 400); }) let graphBut = document.createElement("div") let graphIcon = document.createElement("i") graphBut.dataset.driverid = driver[1] graphBut.dataset.teamid = driver[2] graphIcon.className = "bi bi-graph-up no-pointer pos-relative " graphBut.appendChild(graphIcon) graphBut.className = "GraphButton" graphBut.dataset.state = "unchecked" graphBut.addEventListener("click", function () { if (graphBut.dataset.state === "unchecked") { graphBut.dataset.state = "checked" graphBut.classList.add("activated") graphList.push(graphBut.dataset.driverid) graphTeamList.push(graphBut.dataset.teamid) graphCount += 1 } else if (graphBut.dataset.state === "checked") { graphBut.dataset.state = "unchecked" graphBut.classList.remove("activated") let ind = graphList.indexOf(graphBut.dataset.driverid) graphTeamList.splice(ind, 1) graphList = graphList.filter(x => x !== graphBut.dataset.driverid) graphCount -= 1 } let text = document.querySelector(".graph-text").querySelector(".text-normal") text.innerText = "- " + graphCount + " drivers selected" text.classList.add("graph-highlight"); setTimeout(function () { text.classList.remove("graph-highlight"); }, 400); }) let buttons = document.createElement("div") buttons.classList = "buttons-drivers-modal" let nameAndSurName = document.createElement("div") nameAndSurName.appendChild(spanName) nameAndSurName.appendChild(spanLastName) buttons.appendChild(h2hBut) buttons.append(graphBut) newDiv.appendChild(nameAndSurName) newDiv.appendChild(buttons) manageColor(newDiv, spanLastName) dest.appendChild(newDiv) }); buttonsListeners() applyQueuedAutoCompareDrivers() } document.querySelector(".teams-modal-zone").querySelectorAll(".H2Hradio").forEach(function (h2hBut) { h2hBut.addEventListener("click", function () { if (h2hBut.dataset.state === "unchecked" && h2hCount < 2) { h2hBut.dataset.state = "checked" h2hBut.classList.add("activated") h2hCount += 1 h2hTeamList.push(h2hBut.dataset.teamid) } else if (h2hBut.dataset.state === "checked") { h2hBut.dataset.state = "unchecked" h2hBut.classList.remove("activated") h2hCount -= 1 let ind = h2hTeamList.indexOf(h2hBut.dataset.teamid) h2hTeamList.splice(ind, 1) } let text = document.querySelector(".H2H-text").querySelector(".text-normal") text.innerText = "- " + h2hCount + "/2 teams selected" text.classList.add("h2h-highlight"); setTimeout(function () { text.classList.remove("h2h-highlight"); }, 400); }) }) document.querySelector(".teams-modal-zone").querySelectorAll(".GraphButton").forEach(function (graphBut) { graphBut.addEventListener("click", function () { if (graphBut.dataset.state === "unchecked") { graphBut.dataset.state = "checked" graphBut.classList.add("activated") graphTeamList.push(graphBut.dataset.teamid) graphCount += 1 } else if (graphBut.dataset.state === "checked") { graphBut.dataset.state = "unchecked" graphBut.classList.remove("activated") let ind = graphTeamList.indexOf(graphBut.dataset.teamid) graphTeamList.splice(ind, 1) graphCount -= 1 } let text = document.querySelector(".graph-text").querySelector(".text-normal") text.innerText = "- " + graphCount + " teams selected" text.classList.add("graph-highlight"); setTimeout(function () { text.classList.remove("graph-highlight"); }, 400); }) }) document.querySelector("#driverspillmodal").addEventListener("click", function () { document.querySelector(".drivers-modal-section").classList.remove("d-none") document.querySelector(".teams-modal-section").classList.add("d-none") mode = "driver" resetH2H() }) document.querySelector("#teamspillmodal").addEventListener("click", function () { document.querySelector(".drivers-modal-section").classList.add("d-none") document.querySelector(".teams-modal-section").classList.remove("d-none") mode = "team" resetH2H() }) function buttonsListeners() { document.querySelectorAll("H2HRadio").forEach(function (button) { button.addEventListener("click", function () { }) }) } document.querySelector("#confirmComparison").addEventListener("click", function () { H2HReady() if (h2hCount === 2) { let drivers = document.querySelectorAll(".H2Hradio.activated") let d1 let d2 document.querySelectorAll(".H2Hradio.activated").forEach(function (elem) { if (mode === "driver") { if (elem.dataset.driverid === h2hList[0]) { d1 = elem; } else if (elem.dataset.driverid === h2hList[1]) { d2 = elem } } else if (mode === "team") { if (elem.dataset.teamid === h2hTeamList[0]) { d1 = elem; } else if (elem.dataset.teamid === h2hTeamList[1]) { d2 = elem } } }) nameTitleD1(d1.parentElement.parentElement) nameTitleD2(d2.parentElement.parentElement) } document.querySelector("#compConfigContent").innerText = document.querySelector("#yearButtonH2H").textContent if (mode === "driver") { document.querySelector("#qualiForm").classList.remove("d-none") document.querySelector("#raceForm").classList.remove("d-none") document.querySelector("#gapToWinner").classList.remove("d-none") document.querySelector("#gapToPole").classList.remove("d-none") document.querySelector("#raceForm").click() race = 0 quali = 0 document.getElementById("qualih2h").querySelector(".only-name").innerText = "QUALIFYING" document.getElementById("raceh2h").querySelector(".only-name").innerText = "RACE" document.getElementById("raceh2h").querySelector(".bar-space").classList.remove("d-none") document.getElementById("raceh2h").querySelector(".avg-comparison").classList.add("d-none") document.getElementById("qualih2h").querySelector(".bar-space").classList.remove("d-none") document.getElementById("qualih2h").querySelector(".avg-comparison").classList.add("d-none") } else if (mode === "team") { document.querySelector("#qualiForm").classList.add("d-none") document.querySelector("#raceForm").classList.add("d-none") document.querySelector("#gapToWinner").classList.add("d-none") document.querySelector("#gapToPole").classList.add("d-none") document.querySelector("#pointsProgression").click() race = 0 quali = 0 document.getElementById("qualih2h").querySelector(".only-name").innerText = "QUALIFYING" document.getElementById("raceh2h").querySelector(".only-name").innerText = "RACE" document.getElementById("raceh2h").querySelector(".bar-space").classList.remove("d-none") document.getElementById("raceh2h").querySelector(".avg-comparison").classList.add("d-none") document.getElementById("qualih2h").querySelector(".bar-space").classList.remove("d-none") document.getElementById("qualih2h").querySelector(".avg-comparison").classList.add("d-none") } }) document.querySelector("#clearAll").addEventListener("click", function () { resetH2H() }) export function resetH2H() { h2hCount = 0; graphCount = 0; h2hList = [] graphList = [] h2hTeamList = [] graphTeamList = [] let h2htext = document.querySelector(".H2H-text").querySelector(".text-normal") let graphtext = document.querySelector(".graph-text").querySelector(".text-normal") if (mode === "driver") { h2htext.innerText = "- " + h2hCount + "/2 drivers selected" graphtext.innerText = "- " + graphCount + " drivers selected" } else if (mode === "team") { h2htext.innerText = "- " + h2hCount + "/2 teams selected" graphtext.innerText = "- " + graphCount + " teams selected" } document.querySelector(".teams-modal-zone").querySelectorAll(".H2Hradio").forEach(function (elem) { elem.classList = "H2Hradio" elem.dataset.state = "unchecked" }) document.querySelector(".teams-modal-zone").querySelectorAll(".GraphButton").forEach(function (elem) { elem.classList = "GraphButton" elem.dataset.state = "unchecked" }) document.querySelector(".drivers-modal-zone").querySelectorAll(".H2Hradio").forEach(function (elem) { elem.classList = "H2Hradio" elem.dataset.state = "unchecked" }) document.querySelector(".drivers-modal-zone").querySelectorAll(".GraphButton").forEach(function (elem) { elem.classList = "GraphButton" elem.dataset.state = "unchecked" }) } /** * Event listeners for the 3 types of graphs */ document.querySelector("#pointsProgression").addEventListener("click", function (elem) { document.querySelector("#graphTypeButton span").innerText = "Points progression" document.querySelector("#qualiGraph").classList.add("d-none") document.querySelector("#driverGraph").classList.add("d-none") document.querySelector("#progressionGraph").classList.remove("d-none") document.querySelector("#gapToWinnerGraph").classList.add("d-none") document.querySelector("#gapToPoleGraph").classList.add("d-none") }) document.querySelector("#raceForm").addEventListener("click", function (elem) { document.querySelector("#graphTypeButton span").innerText = "Race form" document.querySelector("#qualiGraph").classList.add("d-none") document.querySelector("#driverGraph").classList.remove("d-none") document.querySelector("#progressionGraph").classList.add("d-none") document.querySelector("#gapToWinnerGraph").classList.add("d-none") document.querySelector("#gapToPoleGraph").classList.add("d-none") }) document.querySelector("#qualiForm").addEventListener("click", function (elem) { document.querySelector("#graphTypeButton span").innerText = "Qualifying form" document.querySelector("#qualiGraph").classList.remove("d-none") document.querySelector("#driverGraph").classList.add("d-none") document.querySelector("#progressionGraph").classList.add("d-none") document.querySelector("#gapToWinnerGraph").classList.add("d-none") document.querySelector("#gapToPoleGraph").classList.add("d-none") }) document.querySelector("#gapToWinner").addEventListener("click", function (elem) { document.querySelector("#graphTypeButton span").innerText = "Gap to winner" document.querySelector("#qualiGraph").classList.add("d-none") document.querySelector("#driverGraph").classList.add("d-none") document.querySelector("#progressionGraph").classList.add("d-none") document.querySelector("#gapToWinnerGraph").classList.remove("d-none") document.querySelector("#gapToPoleGraph").classList.add("d-none") }) document.querySelector("#gapToPole").addEventListener("click", function (elem) { document.querySelector("#graphTypeButton span").innerText = "Gap to pole" document.querySelector("#qualiGraph").classList.add("d-none") document.querySelector("#driverGraph").classList.add("d-none") document.querySelector("#progressionGraph").classList.add("d-none") document.querySelector("#gapToWinnerGraph").classList.add("d-none") document.querySelector("#gapToPoleGraph").classList.remove("d-none") }) /** * Updates the driver 1 name card with the d1 information stored in aDriver1 * @param {a} aDriver1 clickable element of the driver 1 dropdown */ function nameTitleD1(aDriver1) { driver1Sel = aDriver1 if (mode === "driver") { document.querySelector(".driver1-first").classList.remove("d-none") document.querySelector(".driver1-second").classList.remove("d-none") document.querySelector(".team1").classList.add("d-none") document.querySelector(".driver1-first").textContent = driver1Sel.firstChild.children[0].innerText document.querySelector(".driver1-second").textContent = driver1Sel.firstChild.children[1].innerText document.querySelector(".driver1-second").dataset.teamid = driver1Sel.firstChild.children[1].dataset.teamid d1_team = driver1Sel.firstChild.children[1].dataset.teamid document.querySelector(".driver1-second").className = "driver1-second bold-font" manageColor(document.querySelector(".driver1-second"), document.querySelector(".driver1-second")) } else if (mode === "team") { document.querySelector(".driver1-first").classList.add("d-none") document.querySelector(".driver1-second").classList.add("d-none") document.querySelector(".team1").classList.remove("d-none") const teamId = Number(driver1Sel.dataset.teamid) document.querySelector(".team1").dataset.teamid = teamId renderTeamLogo(document.querySelector(".team1"), teamId) } } /** * Updates the driver 2 name card with the d1 information stored in aDriver2 * @param {a} aDriver2 clickable element of the driver 2 dropdown */ function nameTitleD2(aDriver2) { driver2Sel = aDriver2 if (mode === "driver") { document.querySelector(".driver2-first").classList.remove("d-none") document.querySelector(".driver2-second").classList.remove("d-none") document.querySelector(".team2").classList.add("d-none") document.querySelector(".driver2-first").textContent = driver2Sel.firstChild.children[0].innerText document.querySelector(".driver2-second").textContent = driver2Sel.firstChild.children[1].innerText document.querySelector(".driver2-second").dataset.teamid = driver2Sel.firstChild.children[1].dataset.teamid document.querySelector(".driver2-second").className = "driver2-second bold-font" d2_team = driver2Sel.firstChild.children[1].dataset.teamid manageColor(document.querySelector(".driver2-second"), document.querySelector(".driver2-second")) } else if (mode === "team") { document.querySelector(".driver2-first").classList.add("d-none") document.querySelector(".driver2-second").classList.add("d-none") document.querySelector(".team2").classList.remove("d-none") const teamId = Number(driver2Sel.dataset.teamid) document.querySelector(".team2").dataset.teamid = teamId renderTeamLogo(document.querySelector(".team2"), teamId) } } /** * Sends the message that the H2H is properly configured to fetch results */ function H2HReady() { document.querySelector("#mainH2h").classList.remove("d-none") let list1, list2; if (mode === "driver") { list1 = h2hList list2 = graphList } else if (mode === "team") { list1 = h2hTeamList list2 = graphTeamList } let isCurrentYear = false if (document.querySelector("#yearMenuH2H").firstChild.textContent === document.querySelector("#yearButtonH2H").textContent) { isCurrentYear = true } let data = { h2h: h2hCount === 2 ? list1 : -1, graph: list2, year: document.querySelector("#yearButtonH2H").textContent, mode: mode, isCurrentYear: isCurrentYear } manageH2hState() const command = new Command("configuredH2H", data); command.execute(); } function manageH2hState() { if (h2hCount === 2) { document.querySelector(".blocking-h2h").classList.add("d-none") } else { document.querySelector(".blocking-h2h").classList.remove("d-none") document.querySelector(".driver1-name").className = "driver1-name" document.querySelector(".driver2-name").className = "driver2-name" document.querySelector(".driver1-first").textContent = "" document.querySelector(".driver2-first").textContent = "" document.querySelector(".driver1-second").textContent = "" document.querySelector(".driver2-second").textContent = "" document.querySelectorAll(".driver1-bar").forEach(function (bar) { bar.className = "driver1-bar" bar.style.width = "0px" }) document.querySelectorAll(".driver2-bar").forEach(function (bar) { bar.className = "driver2-bar" bar.style.width = "0px" }) document.querySelectorAll(".driver1-number").forEach(function (num) { num.innerText = "" }) document.querySelectorAll(".driver2-number").forEach(function (num) { num.innerText = "" }) } } export function load_labels_initialize_graphs(data) { h2hData = data var labels = []; data[0].forEach(function (elem) { labels.push(races_names[elem[1]]) }) if (typeof driverGraph !== 'undefined' && driverGraph !== null) { driverGraph.destroy(); } if (typeof pointsGraph !== 'undefined' && pointsGraph !== null) { pointsGraph.destroy(); } if (typeof qualiGraph !== 'undefined' && qualiGraph !== null) { qualiGraph.destroy(); } if (typeof gapWinnerGraph !== 'undefined' && gapWinnerGraph !== null) { gapWinnerGraph.destroy(); } if (typeof gapPoleGraph !== 'undefined' && gapPoleGraph !== null) { gapPoleGraph.destroy(); } createPointsChart(labels) if (mode === "driver") { let max = 20 let q2_line = 15 if (game_version === 2024 && custom_team) { max = 22 q2_line = 16 } else { max = 20 } createRaceChart(labels, max) createQualiChart(labels, max, q2_line) createGapCharts(labels) load_graphs_data(data) } else if (mode === "team") { load_teams_points_graph(data) } } export function reload_h2h_graphs() { if (typeof qualiGraph !== 'undefined' && qualiGraph !== null) { qualiGraph.destroy() } if (typeof driverGraph !== 'undefined' && driverGraph !== null) { driverGraph.destroy() } if (typeof pointsGraph !== 'undefined' && pointsGraph !== null) { pointsGraph.destroy() } if (typeof gapWinnerGraph !== 'undefined' && gapWinnerGraph !== null) { gapWinnerGraph.destroy() } if (typeof gapPoleGraph !== 'undefined' && gapPoleGraph !== null) { gapPoleGraph.destroy() } if (h2hData) { load_labels_initialize_graphs(h2hData) } } function load_teams_points_graph(data) { data.forEach(function (team, ind) { if (ind !== 0 && ind !== data.length - 1) { let teamPoints = []; team.forEach(function (driv, index) { let points = get_one_driver_points_format(driv, data) if (teamPoints.length === 0) { teamPoints = [...points]; } else { teamPoints = teamPoints.map((point, index) => { const pointA = point; const pointB = points[index]; if (pointA == null && pointB == null) { return null; } return (pointA ?? 0) + (pointB ?? 0); }); } }) let team_color = colors_dict[graphTeamList[ind - 1] + "0"] pointsGraph.data.datasets.push({ label: combined_dict[graphTeamList[ind - 1]], data: teamPoints, borderColor: team_color, pointBackgroundColor: team_color, borderWidth: 2, pointRadius: 0, pointHoverRadius: 4, fill: false, spanGaps: false, pointHitRadius: 7, datalabels: { color: function () { if (lightColors.indexOf(team_color) !== -1) { return "#272727" } else { return '#eeeef1' } }, backgroundColor: team_color, display: function (context) { if (context.dataIndex === findLastNonNaNIndex(context.dataset.data)) { return true; } else { return false; } }, borderRadius: 5, font: { family: "Formula1Bold" } }, }) } }) pointsGraph.update() } function get_one_driver_points_format(driver, data) { const completedRaceIds = getCompletedRaceIdsSet(data); const pointsByRaceId = new Map(); let d1_points = [0] driver["races"].forEach(function (elem) { const raceId = Number(elem["raceId"]); let ptsThatRace = Number(elem["points"]); if (Number.isNaN(ptsThatRace) || ptsThatRace < 0) { ptsThatRace = 0; } const qualiPts = Number(elem["qualifyingPoints"]); const qPts = (!Number.isNaN(qualiPts) && qualiPts > 0) ? qualiPts : 0; const sprintPts = Number(elem["sprintPoints"]); const sPts = (elem["sprintPoints"] != null && !Number.isNaN(sprintPts) && sprintPts >= 0) ? sprintPts : 0; pointsByRaceId.set(raceId, ptsThatRace + qPts + sPts); }) data[0].forEach(function (elem) { const raceId = Number(elem[0]); const raceCompleted = completedRaceIds.has(raceId); if (!raceCompleted) { d1_points.push(null); return; } if (pointsByRaceId.has(raceId)) { d1_points.push(pointsByRaceId.get(raceId) + d1_points[d1_points.length - 1]) } else { d1_points.push(d1_points[d1_points.length - 1]) } }) d1_points.shift() return d1_points } function load_graphs_data(drivers) { let max_gapPole = 0; let max_gapWinner = 0; const races_ids = drivers[0].map(r => r[0]); // array de raceId en orden const races_done = drivers[drivers.length - 1]; // array de raceId ya corridas const racesDoneSet = new Set(Array.isArray(races_done) ? races_done.map((raceId) => Number(raceId)) : []); // drivers: array de objetos de piloto (NO metas pairTeamPos/pointsInfo aquí) drivers.forEach(function (driv, index) { // saltamos índices de “cabecera” si antes los tenías; ahora no hace falta, // pero mantengo la condición por seguridad si llamas igual que antes if (index !== 0 && index !== drivers.length - 1) { // buffers let d1_res = []; let d1_races = []; let d1_provisonal = []; let d1_points_provisional = []; let d1_points = [0]; let d1_qualis = []; let d1_provisonal_q = []; let d1_provisional_gapW = []; let d1_provisional_gapP = []; let d1_gapWinner = []; let d1_gapPole = []; let d1_backgroundColors = []; let d1_backgroundColorsPole = []; // --- construir arrays base a partir de driv.races --- const races = Array.isArray(driv.races) ? driv.races : []; races.forEach(function (r) { // raceId / orden d1_races.push(Number(r.raceId)); // posiciones carrera / quali d1_provisonal.push(Number(r.finishingPos)); d1_provisonal_q.push(Number(r.qualifyingPos)); // gaps (se guardan como string "(.xxx)" o "NR"/"...L"; respetamos tu formato) const gw = r.gapToWinner; if (typeof gw === "string") { if (gw.slice(-1) !== "L") { // "(0.123)" → "0.123" d1_provisional_gapW.push(gw.slice(1, -1)); } else { d1_provisional_gapW.push(gw); // "…L" → lo marcas como NaN luego } } else if (gw == null) { d1_provisional_gapW.push("NR"); } else { d1_provisional_gapW.push(String(gw)); } const gp = r.gapToPole; if (gp === "NR") { d1_provisional_gapP.push("NR"); } else if (typeof gp === "string") { d1_provisional_gapP.push(gp.slice(1, -1)); // "(0.123)" → "0.123" } else if (gp == null) { d1_provisional_gapP.push("NR"); } else { d1_provisional_gapP.push(String(gp)); } // puntos de esa carrera (+ sprint si existe y no es -1) let ptsThatRace = Number(r.points); if (ptsThatRace === -1) ptsThatRace = 0; const qualiPts = Number(r.qualifyingPoints); const qPts = (qualiPts > 0) ? qualiPts : 0; const sprintPts = (r.sprintPoints != null && r.sprintPoints !== -1) ? Number(r.sprintPoints) : 0; d1_points_provisional.push(ptsThatRace + qPts + sprintPts); }); // --- color del piloto --- const d1Id = graphList[index - 1]; const d1pos = graphList.indexOf(d1Id); let d1_color; if (d1pos === graphTeamList.indexOf(String(driv.latestTeamId))) { d1_color = colors_dict[driv.latestTeamId + "0"]; } else { d1_color = colors_dict[driv.latestTeamId + "1"]; } races_ids.forEach(function (ridRaw) { const rid = Number(ridRaw); const idx = d1_races.indexOf(rid); const raceCompleted = racesDoneSet.has(rid); if (idx !== -1 && raceCompleted) { // resultado carrera if (d1_provisonal[idx] === -1) { d1_res.push(null); d1_gapWinner.push(null); d1_backgroundColors.push(d1_color + "50"); } else { d1_res.push(d1_provisonal[idx]); // gap winner const gwRaw = d1_provisional_gapW[idx]; if (typeof gwRaw === "string" && gwRaw.slice(-1) === "L") { d1_gapWinner.push(null); d1_backgroundColors.push(d1_color + "76"); } else { const gw = parseFloat(gwRaw); d1_gapWinner.push(gw); if (gw > max_gapWinner) max_gapWinner = gw; d1_backgroundColors.push(d1_color); } } // puntos acumulados d1_points.push(d1_points_provisional[idx] + d1_points[d1_points.length - 1]); // quali d1_qualis.push(d1_provisonal_q[idx]); // gap pole const gpRaw = d1_provisional_gapP[idx]; if (gpRaw === "NR") { d1_gapPole.push(null); d1_backgroundColorsPole.push(d1_color + "60"); } else { const gp = parseFloat(gpRaw); d1_gapPole.push(gp); if (gp > max_gapPole) max_gapPole = gp; d1_backgroundColorsPole.push(d1_color); } } else { // no corrió / no hay datos para este rid d1_res.push(null); d1_qualis.push(null); if (raceCompleted) { d1_points.push(d1_points[d1_points.length - 1]); } else { d1_points.push(null); } } }); // quitamos el 0 inicial d1_points.shift(); // reemplace NaNs de gaps por la mitad del máximo (tu lógica) d1_gapWinner = d1_gapWinner.map(function (elem) { return elem == null ? max_gapWinner / 2 : elem; }); d1_gapPole = d1_gapPole.map(function (elem) { return elem == null ? max_gapPole / 2 : elem; }); // ---- push datasets a los gráficos (Chart.js) ---- driverGraph.data.datasets.push({ label: driv.driverName, data: d1_res, borderColor: d1_color, pointBackgroundColor: d1_color, borderWidth: 2, fill: false, spanGaps: false, pointHitRadius: 7 }); qualiGraph.data.datasets.push({ label: driv.driverName, data: d1_qualis, borderColor: d1_color, pointBackgroundColor: d1_color, borderWidth: 2, fill: false, spanGaps: false, pointHitRadius: 7 }); pointsGraph.data.datasets.push({ label: driv.driverName, data: d1_points, borderColor: d1_color, pointBackgroundColor: d1_color, pointRadius: 0, pointHoverRadius: 4, fill: false, spanGaps: false, pointHitRadius: 7, datalabels: { color: function () { return (lightColors.indexOf(d1_color) !== -1) ? "#272727" : "#eeeef1"; }, backgroundColor: d1_color, display: function (context) { return context.dataIndex === findLastNonNaNIndex(context.dataset.data); }, borderRadius: 5, font: { family: "Formula1Bold" } } }); gapWinnerGraph.options.scales.y.max = max_gapWinner; gapPoleGraph.options.scales.y.max = max_gapPole; gapWinnerGraph.data.datasets.push({ label: driv.driverName, data: d1_gapWinner, borderColor: d1_color, pointBackgroundColor: d1_color, backgroundColor: d1_backgroundColors, borderWidth: 1, fill: true, pointHitRadius: 7 }); gapPoleGraph.data.datasets.push({ label: driv.driverName, data: d1_gapPole, borderColor: d1_color, pointBackgroundColor: d1_color, backgroundColor: d1_backgroundColorsPole, borderWidth: 1, fill: true, pointHitRadius: 7 }); } }); // actualiza driverGraph.update(); qualiGraph.update(); pointsGraph.update(); gapWinnerGraph.update(); gapPoleGraph.update(); } /** * Finds tha last non NaN element in an array * @param {Array} arr array in which the function will look * @returns the indef on which is the last non NaN or -1 if there is none */ function findLastNonNaNIndex(arr) { for (let i = arr.length - 1; i >= 0; i--) { if (arr[i] != null) { return i; } } return -1; // Devuelve -1 si todos los valores son NaN } function updateMaxYAxis(newMax) { driverGraph.options.scales.y.max = newMax; qualiGraph.options.scales.y.max = newMax; driverGraph.update(); qualiGraph.update(); } function getNoEntryAnimationWithHoverTransition() { return { animation: { duration: 0 }, transitions: { active: { animation: { duration: 180, easing: 'easeOutQuad' } } } }; } /** * Creates the head to head race chart * @param {Array} labelsArray array with all the labels for the races */ function createRaceChart(labelsArray, max) { const dataD = { labels: labelsArray, }; driverGraph = new Chart( document.getElementById('driverGraph'), { type: 'line', data: dataD, options: { responsive: true, maintainAspectRatio: false, ...getNoEntryAnimationWithHoverTransition(), interaction: { mode: 'index' }, layout: { padding: { top: 10, right: 25, boottom: 20, left: 10 } }, scales: { x: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } }, y: { reverse: true, min: 1, max: max, grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } } }, plugins: { datalabels: { display: false }, annotation: { annotations: { line1: { type: 'line', display: annotationsToggle, yMin: 1, yMax: 1, borderColor: '#FDE06B', borderWidth: 1, }, line2: { type: 'line', display: annotationsToggle, yMin: 2, yMax: 2, borderColor: '#AEB2B8', borderWidth: 1, }, line3: { type: 'line', display: annotationsToggle, yMin: 3, yMax: 3, borderColor: '#d7985a', borderWidth: 1, }, line4: { type: 'line', display: annotationsToggle, yMin: 10, yMax: 10, borderColor: theme_colors[selectedTheme].labels, borderWidth: 1, } } }, legend: { labels: { boxHeight: 2, boxWidth: 25, color: theme_colors[selectedTheme].labels, font: { family: "Formula1" } }, }, tooltip: { titleFont: { family: 'Formula1Bold', size: 16 }, bodyFont: { family: 'Formula1', size: 14 } } } } } ); } /** * Creates the head to head qualifying chart * @param {Array} labelsArray array with all the labels for the races */ function createQualiChart(labelsArray, max, q2_line) { const dataD = { labels: labelsArray, }; qualiGraph = new Chart( document.getElementById('qualiGraph'), { type: 'line', data: dataD, options: { responsive: true, maintainAspectRatio: false, ...getNoEntryAnimationWithHoverTransition(), interaction: { mode: 'index' }, layout: { padding: { top: 10, right: 25, boottom: 20, left: 10 } }, scales: { x: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } }, y: { reverse: true, min: 1, max: max, grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } } }, plugins: { datalabels: { display: false }, annotation: { annotations: { line1: { type: 'line', display: annotationsToggle, yMin: q2_line, yMax: q2_line, borderColor: 'red', borderWidth: 1, label: { display: true, color: "white", backgroundColor: "red", content: 'Q2', position: 'start', font: { family: "Formula1Bold", size: 12 } } }, line2: { type: 'line', display: annotationsToggle, yMin: 10, yMax: 10, borderColor: 'red', borderWidth: 1, label: { color: "white", display: true, backgroundColor: "red", content: 'Q3', position: 'start', font: { family: "Formula1Bold", size: 12 } } } } }, legend: { labels: { boxHeight: 2, boxWidth: 25, color: theme_colors[selectedTheme].labels, font: { family: "Formula1" } }, }, tooltip: { titleFont: { family: 'Formula1Bold', size: 16 }, bodyFont: { family: 'Formula1', size: 14 } } } } } ); } /** * Creates the head to head qualifying chart * @param {Array} labelsArray array with all the labels for the races */ function createPointsChart(labelsArray) { const dataD = { labels: labelsArray, }; pointsGraph = new Chart( document.getElementById('progressionGraph'), { type: 'line', data: dataD, options: { responsive: true, maintainAspectRatio: false, ...getNoEntryAnimationWithHoverTransition(), interaction: { mode: 'index' }, layout: { padding: { top: 10, right: 25, boottom: 20, left: 10 } }, scales: { x: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } }, y: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } } }, plugins: { legend: { labels: { boxHeight: 2, boxWidth: 25, color: theme_colors[selectedTheme].labels, font: { family: "Formula1" } }, }, tooltip: { titleFont: { family: 'Formula1Bold', size: 16 }, bodyFont: { family: 'Formula1', size: 14 } } } } } ); } function createGapCharts(labelsArray, maxGapWinner, maxGapPole) { const dataD1 = { labels: labelsArray, }; const dataD2 = { labels: labelsArray, }; let commonOptions = { responsive: true, maintainAspectRatio: false, ...getNoEntryAnimationWithHoverTransition(), interaction: { mode: 'index' }, scales: { x: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } }, y: { min: 0, grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } } }, plugins: { datalabels: { display: false }, legend: { labels: { boxHeight: 2, boxWidth: 25, color: theme_colors[selectedTheme].labels, font: { family: "Formula1" } }, }, tooltip: { titleFont: { family: 'Formula1Bold', size: 16 }, bodyFont: { family: 'Formula1', size: 14 }, callbacks: { label: function (tooltipItem) { let dataset = tooltipItem.dataset; // Acceder al dataset actual let index = tooltipItem.dataIndex; // Obtener el índice del dato let color = dataset.backgroundColor[index]; // Obtener el color de fondo del dato actual let result; if (color.endsWith("50")) { result = 'DNF'; } else if (color.endsWith("60")) { result = "Not representative" } else if (color.endsWith("76")) { result = "Lapped" } else { result = `${tooltipItem.raw}s`; } // Mostrar el nombre del piloto y el resultado (valor o DNF) return `${dataset.label}: ${result}`; } } } }, options: { layout: { padding: { top: 10, right: 25, boottom: 20, left: 10 } } } }; let gapWinnerOptions = { ...commonOptions, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: maxGapWinner, } }, plugins: { ...commonOptions.plugins } }; gapWinnerGraph = new Chart( document.getElementById('gapToWinnerGraph'), { type: 'bar', data: dataD1, options: gapWinnerOptions } ); let gapPoleOptions = { ...commonOptions, scales: { ...commonOptions.scales, y: { ...commonOptions.scales.y, max: 20 } }, plugins: { ...commonOptions.plugins, } }; gapPoleGraph = new Chart( document.getElementById('gapToPoleGraph'), { type: 'bar', data: dataD2, options: gapPoleOptions } ); } ================================================ FILE: src/js/frontend/news.js ================================================ import { team_dict, combined_dict, races_names, names_full, countries_data, logos_disc, lightColors, part_full_names, CUSTOM_NEWS_TYPE_META, CUSTOM_NEWS_INVESTMENT_COUNTRIES, CUSTOM_NEWS_DSQ_COMPONENTS, CUSTOM_NEWS_ENGINE_CHANGE_AREAS, CUSTOM_NEWS_ADUO_QUARTERS, CUSTOM_NEWS_IMAGE_FILES } from "./config"; import { Command } from "../backend/command"; import { getCircuitInfo } from "../backend/scriptUtils/newsUtils"; import newsPromptsTemaplates from "../../data/news/news_prompts_templates.json"; import turningPointsTemplates from "../../data/news/turning_points_prompts_templates.json"; import newsTitleTemplates from "../../data/news/news_titles_templates.json"; import turningPointTitleTemplates from "../../data/news/turning_points_titles_templates.json"; import { currentSeason } from "./transfers"; import { colors_dict } from "./head2head"; import { excelToDate } from "../backend/scriptUtils/eidtStatsUtils"; import { generateNews, getSaveName, confirmModal, updateRateLimitsDisplay, new_update_notifications } from "./renderer"; import { marked } from 'marked'; import TurndownService from "turndown"; import DOMPurify from "dompurify"; import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js"; const newsGrid = document.querySelector('.news-grid'); const newsModalEl = document.getElementById('newsModal'); const closeBtn = document.getElementById('closeNewsArticle'); const newsOptionsBtn = document.querySelector('.news-options'); const copyArticleBtn = document.getElementById('copyArticle'); const deleteArticleBtn = document.getElementById('deleteArticle'); const editArticleBtn = document.getElementById('editArticle'); const createCustomNewsBtn = document.getElementById('createCustomNews'); const customNewsModalEl = document.getElementById('customNewsModal'); const customNewsTypeButton = document.getElementById('customNewsTypeButton'); const customNewsTypeMenu = document.getElementById('customNewsTypeMenu'); const customNewsDateInput = document.getElementById('customNewsDateInput'); const customNewsTemplateWrap = document.getElementById('customNewsTemplateWrap'); const customNewsTemplateButton = document.getElementById('customNewsTemplateButton'); const customNewsTemplateMenu = document.getElementById('customNewsTemplateMenu'); const customNewsTemplateHelp = document.getElementById('customNewsTemplateHelp'); const customNewsTitleInput = document.getElementById('customNewsTitleInput'); const customNewsParams = document.getElementById('customNewsParams'); const customNewsError = document.getElementById('customNewsError'); const customNewsCreateBtn = document.getElementById('customNewsCreateBtn'); let interval2 = null; let cleaning = false; let currentModalNews = null; let currentModalExtraContext = ''; let isEditingArticle = false; let originalArticleHTML = ''; let editTextarea = null; let editTitleInput = null; let saveArticleBtn = null; let cancelArticleBtn = null; let originalTitleText = ''; let cachedNewsAvailable = { normal: false, turning: false }; function isPaidNewsMember() { return window.__USER_DATA__?.paidMember || false; } function canUseGenAiForNews(news) { if (news?.type === "turning_point_aduo") { return isPaidNewsMember(); } return true; } function setAduoLockedHidden(el, hidden) { if (!el) return; if (hidden) { el.classList.add('d-none'); el.dataset.aduoLocked = '1'; } else if (el.dataset.aduoLocked === '1') { el.classList.remove('d-none'); delete el.dataset.aduoLocked; } } function applyNewsModalGenAiLocks(news) { const lockGenAi = news?.type === "turning_point_aduo" && !canUseGenAiForNews(news); const regenerateButton = document.getElementById('regenerateArticle'); const regenerateWithContextButton = document.getElementById('regenerateWithContext'); const optionsContextContainer = document.getElementById('newsOptionsContext'); if (lockGenAi) { setOptionsContextOpen(false); } setAduoLockedHidden(regenerateButton, lockGenAi); setAduoLockedHidden(regenerateWithContextButton, lockGenAi); setAduoLockedHidden(optionsContextContainer, lockGenAi); } const DEFAULT_NEWS_LANGUAGE = "English"; const NEWS_LANGUAGE_STORAGE_KEY = "newsLanguage"; const NEWS_LANGUAGE_OPTIONS = [ { value: "English", label: "English" }, { value: "Spanish", label: "Spanish" }, { value: "Italian", label: "Italian" }, { value: "French", label: "French" }, { value: "German", label: "German" }, { value: "Dutch", label: "Dutch" }, { value: "Polish", label: "Polish" }, { value: "Portuguese", label: "Portuguese" }, { value: "Russian", label: "Russian" }, { value: "Chinese", label: "Chinese" }, { value: "Japanese", label: "Japanese" }, ]; const wait = (ms) => new Promise(r => setTimeout(r, ms)); const onTransitionEnd = (el, propName, timeoutMs) => new Promise(resolve => { let done = false; const handler = (e) => { if (!propName || e.propertyName === propName) { done = true; el.removeEventListener('transitionend', handler); resolve(); } }; el.addEventListener('transitionend', handler, { once: true }); if (timeoutMs != null) { setTimeout(() => { if (!done) { el.removeEventListener('transitionend', handler); resolve(); } }, timeoutMs); } }); async function finishGeneralLoader() { const pageLoaderDiv = document.querySelector('.general-news-loader'); if (!pageLoaderDiv) return; const pageProgressDiv = pageLoaderDiv.querySelector('.general-news-progress-div') || document.querySelector('.general-news-progress-div'); const id = pageProgressDiv?._progressIntervalId; if (id) { clearInterval(id); pageProgressDiv._progressIntervalId = null; } if (pageProgressDiv) { await new Promise(requestAnimationFrame); // asegura estado inicial pageProgressDiv.style.width = '100%'; await Promise.race([ onTransitionEnd(pageProgressDiv, 'width', 220), wait(200) ]); } pageLoaderDiv.style.opacity = '0'; await Promise.race([ onTransitionEnd(pageLoaderDiv, 'opacity', 150), wait(100) ]); pageLoaderDiv.remove(); } function getNewsLanguage() { try { return localStorage.getItem(NEWS_LANGUAGE_STORAGE_KEY) || DEFAULT_NEWS_LANGUAGE; } catch { return DEFAULT_NEWS_LANGUAGE; } } function replaceLanguagePlaceholder(text, language) { if (typeof text !== 'string') return text; return text.replace(/{{\s*language\s*}}/gi, language); } function syncNewsLanguageDropdown(selectedLanguage) { const menu = document.getElementById('newsLanguageMenu'); const button = document.getElementById('newsLanguageButton'); if (button) { const label = button.querySelector('span'); if (label) { label.innerText = selectedLanguage; } } if (menu) { menu.querySelectorAll('.redesigned-dropdown-item').forEach(item => { const isSelected = item.dataset.value === selectedLanguage; item.querySelector('i')?.classList.toggle('unactive', !isSelected); }); } } function setNewsLanguage(language) { const selected = NEWS_LANGUAGE_OPTIONS.find(opt => opt.value === language)?.value || DEFAULT_NEWS_LANGUAGE; try { localStorage.setItem(NEWS_LANGUAGE_STORAGE_KEY, selected); } catch { // Ignore storage errors and keep using the selected value in memory } syncNewsLanguageDropdown(selected); } function setupNewsLanguageDropdown() { const menu = document.getElementById('newsLanguageMenu'); const button = document.getElementById('newsLanguageButton'); if (!menu || !button) return; menu.innerHTML = ''; NEWS_LANGUAGE_OPTIONS.forEach(({ value, label }) => { const item = document.createElement('a'); item.classList.add('redesigned-dropdown-item'); item.dataset.value = value; item.href = '#'; item.innerText = label; const checkIcon = document.createElement('i'); checkIcon.classList.add('bi', 'bi-check'); item.appendChild(checkIcon); item.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); setNewsLanguage(value); }); menu.appendChild(item); }); syncNewsLanguageDropdown(getNewsLanguage()); } async function cleanupOpenedNewsItem() { if (cleaning) return; cleaning = true; const newsItem = document.querySelector('.news-item.opened'); if (newsItem) { newsItem.classList.remove('opened'); // Espera a que termine la transición del item (tu utilidad) await onTransitionEnd(newsItem, 'transform', 150); newsItem.classList.remove('with-transition'); } cleaning = false; } closeBtn.addEventListener('click', async () => { await cleanupOpenedNewsItem(); const modal = bootstrap.Modal.getInstance(newsModalEl) || new bootstrap.Modal(newsModalEl); modal.hide(); }); newsModalEl.addEventListener('hide.bs.modal', () => { exitArticleEditMode(); cleanupOpenedNewsItem(); }); function exitArticleEditMode(opts = {}) { const { restoreOriginal = true } = opts; if (!isEditingArticle) return; const newsArticle = document.querySelector('#newsModal .news-article'); const modalTitle = document.querySelector('#newsModal .modal-title'); if (newsArticle && restoreOriginal) { newsArticle.innerHTML = originalArticleHTML; } if (modalTitle && restoreOriginal) { modalTitle.innerHTML = originalTitleText; } if (saveArticleBtn) saveArticleBtn.remove(); if (cancelArticleBtn) cancelArticleBtn.remove(); closeBtn?.classList.remove('d-none'); isEditingArticle = false; originalArticleHTML = ''; editTextarea = null; editTitleInput = null; saveArticleBtn = null; cancelArticleBtn = null; originalTitleText = ''; } function hashStr(str) { let h = 2166136261 >>> 0; for (let i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619) >>> 0; } return h >>> 0; } const BUCKET_TURNING = 5; const BUCKET_NORMAL = 7; async function openContextModal(articleTitle = '') { const modalEl = document.getElementById('newsContextModal'); if (!modalEl) return { confirmed: false, context: '' }; const bsModal = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl, { keyboard: false }); const modalTitle = modalEl.querySelector('.modal-title'); const textarea = modalEl.querySelector('.news-context-textarea'); const confirmBtn = modalEl.querySelector('.context-confirm'); const cancelBtn = modalEl.querySelector('.context-cancel'); if (modalTitle) { modalTitle.textContent = articleTitle ? `Add context for "${articleTitle}"` : "Add context for this article"; } if (textarea) { textarea.value = ''; } return new Promise((resolve) => { let clicked = false; const controller = new AbortController(); const { signal } = controller; confirmBtn?.addEventListener('click', () => { clicked = true; resolve({ confirmed: true, context: textarea?.value?.trim() || '' }); bsModal.hide(); }, { once: true, signal }); cancelBtn?.addEventListener('click', () => { clicked = true; resolve({ confirmed: false, context: '' }); bsModal.hide(); }, { once: true, signal }); modalEl.addEventListener('hidden.bs.modal', () => { if (!clicked) resolve({ confirmed: false, context: '' }); controller.abort(); }, { once: true }); modalEl.addEventListener('shown.bs.modal', () => { textarea?.focus(); }, { once: true }); bsModal.show(); }); } function setOptionsContextOpen(isOpen) { const container = document.getElementById('newsOptionsContext'); if (!container) return; container.classList.toggle('open', isOpen); container.setAttribute('aria-hidden', isOpen ? 'false' : 'true'); if (isOpen) { setTimeout(() => { const textarea = document.getElementById('newsOptionsContextTextarea'); textarea?.focus(); }, 0); } } async function openNewsModalFlow(news, newsItem, newsList, opts = {}) { const { extraContext = '' } = opts; exitArticleEditMode(); currentModalNews = news; currentModalExtraContext = extraContext; newsItem?.classList.add('with-transition', 'opened'); applyNewsModalGenAiLocks(news); const newsModal = new bootstrap.Modal(document.getElementById('newsModal'), { keyboard: false }); if (!news.text) { newsOptionsBtn.classList.add('d-none'); } else { newsOptionsBtn.classList.remove('d-none'); } newsModal._element.setAttribute("data-article-id", news.id || ''); newsOptionsBtn?.classList.remove('active'); setOptionsContextOpen(false); const optionsContextTextarea = document.getElementById('newsOptionsContextTextarea'); if (optionsContextTextarea) { optionsContextTextarea.value = currentModalExtraContext || ''; } newsModal.show(); const modalTitle = document.querySelector('#newsModal .modal-title'); modalTitle.textContent = news.title; const newsArticle = document.querySelector('#newsModal .news-article'); newsArticle.innerHTML = ''; const dateSpan = document.querySelector('#newsModal .news-article-date .dateSpan'); const date = excelToDate(news.date); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); dateSpan.textContent = `${day}/${month}/${year}`; const image = document.querySelector('#newsModal .news-image-background'); (async () => { const url = news.image; const exists = await imageExists(url); if (exists) { image.classList.remove('d-none'); image.src = news.image; } else { image.classList.add('d-none'); } })(); await generateAndRenderArticle(news, newsList, "Generating", false, undefined, extraContext); const optionsContextCancelButton = document.getElementById('newsOptionsContextCancel'); if (optionsContextCancelButton) { optionsContextCancelButton.replaceWith(optionsContextCancelButton.cloneNode(true)); const newOptionsContextCancelButton = document.getElementById('newsOptionsContextCancel'); newOptionsContextCancelButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (optionsContextTextarea) optionsContextTextarea.value = currentModalExtraContext || ''; setOptionsContextOpen(false); }); } const optionsContextRegenerateButton = document.getElementById('newsOptionsContextRegenerate'); if (optionsContextRegenerateButton) { optionsContextRegenerateButton.replaceWith(optionsContextRegenerateButton.cloneNode(true)); const newOptionsContextRegenerateButton = document.getElementById('newsOptionsContextRegenerate'); newOptionsContextRegenerateButton.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); currentModalExtraContext = optionsContextTextarea?.value?.trim() || ''; setOptionsContextOpen(false); newsOptionsBtn?.classList.remove('active'); await generateAndRenderArticle(news, newsList, "Regenerating", true, undefined, currentModalExtraContext); }); } const regenerateButton = document.getElementById('regenerateArticle'); if (regenerateButton) { regenerateButton.replaceWith(regenerateButton.cloneNode(true)); const newRegenerateButton = document.getElementById('regenerateArticle'); newRegenerateButton.addEventListener('click', async () => { await generateAndRenderArticle(news, newsList, "Regenerating", true, undefined, currentModalExtraContext); }); } const regenerateWithContextButton = document.getElementById('regenerateWithContext'); if (regenerateWithContextButton) { regenerateWithContextButton.replaceWith(regenerateWithContextButton.cloneNode(true)); const newRegenerateWithContextButton = document.getElementById('regenerateWithContext'); newRegenerateWithContextButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); if (optionsContextTextarea) { optionsContextTextarea.value = currentModalExtraContext || ''; } const container = document.getElementById('newsOptionsContext'); const isOpen = !!container?.classList.contains('open'); setOptionsContextOpen(!isOpen); }); } } function addReadButtonListener(readButton, newsItem, news, newsList) { readButton.addEventListener('click', async () => { await openNewsModalFlow(news, newsItem, newsList); }); } async function generateAndRenderArticle(news, newsList, label = "Generating", force = false, model, extraContext = '') { exitArticleEditMode(); const newsArticle = document.querySelector('#newsModal .news-article'); newsArticle.innerHTML = ''; const loaderDiv = document.createElement('div'); loaderDiv.classList.add('loader-div'); const loadingSpan = document.createElement('span'); loadingSpan.textContent = label; const loadingDots = document.createElement('span'); loadingDots.textContent = "."; loadingDots.classList.add('loading-dots'); loadingSpan.appendChild(loadingDots); const dotsInterval = setInterval(() => { loadingDots.textContent = loadingDots.textContent.length >= 3 ? "." : loadingDots.textContent + "."; }, 500); const progressBar = document.createElement('div'); progressBar.classList.add('ai-progress-bar'); const progressDiv = document.createElement('div'); progressDiv.classList.add('progress-div'); progressBar.appendChild(progressDiv); loaderDiv.appendChild(loadingSpan); loaderDiv.appendChild(progressBar); newsArticle.appendChild(loaderDiv); let progress = 0; const interval = setInterval(() => { progress += 2; if (progressDiv) progressDiv.style.width = progress + '%'; if (progress >= 20) clearInterval(interval); }, 150); try { const articleText = await manageRead(news, newsList, progressDiv, interval, { force, extraContext }); clearInterval(interval); clearInterval(dotsInterval); progressDiv.style.width = '100%'; setTimeout(() => { loaderDiv.style.opacity = '0'; newsArticle.style.opacity = '0'; setTimeout(() => { loaderDiv.remove(); const rawHtml = marked.parse(articleText); const cleanHtml = DOMPurify.sanitize(rawHtml); newsArticle.innerHTML = cleanHtml; newsArticle.style.opacity = '1'; newsOptionsBtn.classList.remove('d-none'); }, 150); }, 200); } catch (err) { console.error("Error generating article:", err); clearInterval(interval); clearInterval(dotsInterval); loaderDiv.remove(); const errorDiv = document.createElement('div'); errorDiv.classList.add('news-error', 'model-error'); if (err.status === 429) { errorDiv.innerText = "Daily limit reached. Tomorrow you'll be able to generate more articles."; newsArticle.appendChild(errorDiv); } else { errorDiv.innerText = "Error generating article. Please try again."; newsArticle.appendChild(errorDiv); } } } function manageTurningPointButtons(news, newsList, maxDate, newsBody, readbuttonContainer, newsAvailable) { let approveButton, randomButton, cancelButton; const isAduoTurningPoint = news.type === "turning_point_aduo"; const isFreeTier = !window.__USER_DATA__?.paidMember if (isFreeTier && news.hiddenByAvailability && !isAduoTurningPoint) { return; } if (news.turning_point_type === "original") { const tpDiv = document.createElement('div'); tpDiv.classList.add('turning-point-div'); cancelButton = document.createElement('div'); cancelButton.classList.add('cancel-tp', 'tp-button'); const cancelIcon = document.createElement('i'); cancelIcon.classList.add('bi', 'bi-x', 'tp-icon'); cancelButton.appendChild(cancelIcon); tpDiv.appendChild(cancelButton); cancelButton.addEventListener('click', async () => { randomButton.remove(); approveButton.remove(); cancelButton.classList.add('tp-button-selected'); const resultSpan = document.createElement('span'); resultSpan.classList.add('tp-result-span'); resultSpan.innerText = "Cancelled"; cancelButton.innerHTML = ''; cancelButton.appendChild(resultSpan); cancelButton.replaceWith(cancelButton.cloneNode(true)); const command = new Command("cancelTurningPoint", { turningPointData: news.data, type: news.type, maxDate: maxDate, id: news.id }); let newResp = await command.promiseExecute(); place_turning_outcome(newResp.content, newsList); }); randomButton = document.createElement('div'); randomButton.classList.add('random-tp', 'tp-button'); const randomIcon = document.createElement('i'); randomIcon.classList.add('bi', 'bi-shuffle', 'tp-icon'); randomButton.appendChild(randomIcon); tpDiv.appendChild(randomButton); approveButton = document.createElement('div'); approveButton.classList.add('approve-tp', 'tp-button'); const approveIcon = document.createElement('i'); approveIcon.classList.add('bi', 'bi-check', 'tp-icon'); approveButton.appendChild(approveIcon); tpDiv.appendChild(approveButton); let nonReadable = false; approveButton.addEventListener('click', async () => { //has the news text if (!news.text || news.text.length === 0) { const ok = await confirmModal({ title: "Approve Turning Point", body: "Are you sure you want to approve this turning point? If you approve it before reading the article, it will not be able to generate the article further down the line.", confirmText: "Approve", cancelText: "Cancel" }); if (!ok) return; else { const readButton = newsBody.querySelector('.read-button-container .read-button'); readButton.remove(); nonReadable = true; } } //remove the other 2 buttons randomButton.remove(); cancelButton.remove(); approveButton.classList.add('tp-button-selected'); //remove the icon and add text "Approved" const resultSpan = document.createElement('span'); resultSpan.classList.add('tp-result-span'); resultSpan.innerText = "Approved"; approveButton.innerHTML = ''; approveButton.appendChild(resultSpan); //remove the eventListener approveButton.replaceWith(approveButton.cloneNode(true)); const command = new Command("approveTurningPoint", { turningPointData: news.data, type: news.type, maxDate: maxDate, id: news.id, nonReadable: nonReadable === true }); const newResp = await command.promiseExecute(); place_turning_outcome(newResp.content, newsList); if (news.type === "turning_point_transfer" || news.type === "turning_point_injury" || news.type === "turning_point_young_drivers" || news.type === "turning_point_young_drivers") { const commandDrivers = new Command("driversRefresh", {}); commandDrivers.execute(); } else if (news.type === "turning_point_technical_directive") { const commandTechDir = new Command("performanceRefresh", {}); commandTechDir.execute(); } else if (news.type === "turning_point_race_substitution") { const commandYear = new Command("yearSelected", { year: news.data.season, isCurrentYear: true }); commandYear.execute(); const commandCalendar = new Command("calendarRefresh", {}); commandCalendar.execute(); } else if (news.type === "turning_point_engine_regulation") { const commandEngines = new Command("enginesRefresh", {}); commandEngines.execute(); } else if (news.type === "turning_point_aduo") { const commandEngines = new Command("enginesRefresh", {}); commandEngines.execute(); const commandPerformance = new Command("performanceRefresh", {}); commandPerformance.execute(); } }); randomButton.addEventListener('click', () => { // Evita dobles clics randomButton.classList.add('tp-button-selected'); const span = document.createElement('span'); span.classList.add('tp-result-span'); span.innerText = 'Deciding...'; randomButton.innerHTML = ''; randomButton.appendChild(span); randomButton.style.pointerEvents = 'none'; // 50-50 const approve = Math.random() < 0.5; if (approve && approveButton?.isConnected) { approveButton.click(); } else if (cancelButton?.isConnected) { cancelButton.click(); } else if (approveButton) { approveButton.click(); } }); readbuttonContainer.appendChild(tpDiv); } else if (news.turning_point_type === "approved") { const tpDiv = document.createElement('div'); tpDiv.classList.add('turning-point-div'); const approvedButton = document.createElement('div'); approvedButton.classList.add('approve-tp', 'tp-button', 'tp-button-selected'); const approvedSpan = document.createElement('span'); approvedSpan.classList.add('tp-result-span'); approvedSpan.innerText = "Approved"; approvedButton.appendChild(approvedSpan); tpDiv.appendChild(approvedButton); readbuttonContainer.appendChild(tpDiv); } else if (news.turning_point_type === "cancelled") { const tpDiv = document.createElement('div'); tpDiv.classList.add('turning-point-div'); const cancelledButton = document.createElement('div'); cancelledButton.classList.add('cancel-tp', 'tp-button', 'tp-button-selected'); const cancelledSpan = document.createElement('span'); cancelledSpan.classList.add('tp-result-span'); cancelledSpan.innerText = "Cancelled"; cancelledButton.appendChild(cancelledSpan); tpDiv.appendChild(cancelledButton); readbuttonContainer.appendChild(tpDiv); } if (!newsAvailable.turning && !isAduoTurningPoint) { const showInsiderModal = async () => { await confirmModal({ title: "Insider News Unavailable", body: "Insider news are currently unavailable. To unlock insider news and be able to decide the outcome of turning points, please consider subscribing to the INSIDER tier on our Patreon page.", confirmText: "Okay" }); }; const swap = (btn) => { if (!btn) return null; const clone = btn.cloneNode(true); // clona (sin listeners) btn.replaceWith(clone); // mete el clon en el DOM clone.addEventListener('click', showInsiderModal); // añade el nuevo listener return clone; // devuelve la nueva referencia }; // ¡OJO!: usa let para poder reasignar approveButton = swap(approveButton); randomButton = swap(randomButton); cancelButton = swap(cancelButton); } } function createNewsItemElement(news, index, newsAvailable, newsList, maxDate, isCurrentSeason = true) { const isTurning = news.turning_point_type === 'original' || news.turning_point_type === 'approved' || news.turning_point_type === 'cancelled'; const isAduoTurningPoint = news.type === "turning_point_aduo"; const newsItem = document.createElement('div'); newsItem.classList.add('news-item', 'fade-in'); newsItem.setAttribute('style', '--order: ' + (index + 1)); if (news.hiddenByAvailability) { newsItem.dataset.hiddenReason = news.hiddenReason || 'none'; newsItem.classList.add('hidden-by-availability'); // para que lo estilices si quieres } const newsBody = document.createElement('div'); newsBody.classList.add('news-body'); const titleAndArticle = document.createElement('div'); titleAndArticle.classList.add('title-and-article'); const newsTitle = document.createElement('span'); newsTitle.classList.add('news-title', 'bold-font'); newsTitle.textContent = news.title; const imageContainer = document.createElement('div'); imageContainer.classList.add('news-image-container'); const readbuttonContainer = document.createElement('div'); readbuttonContainer.classList.add('read-button-container'); const readActions = document.createElement('div'); readActions.classList.add('read-actions'); const contextButton = document.createElement('div'); contextButton.classList.add('context-read-button'); contextButton.setAttribute('title', 'Add context'); const contextIcon = document.createElement('i'); contextIcon.classList.add('bi', 'bi-chat-text'); contextButton.appendChild(contextIcon); const readButton = document.createElement('div'); readButton.classList.add('read-button'); const readButtonSpan = document.createElement('span'); readButtonSpan.innerText = "Read"; readButton.appendChild(readButtonSpan); manage_overlay(imageContainer, news.overlay, news.data, news.image); const image = document.createElement('img'); image.classList.add('news-image'); image.setAttribute('data-src', news.image); image.src = news.image; image.setAttribute('loading', 'lazy'); imageContainer.appendChild(image); if (news.hiddenByAvailability) { const blockedDiv = document.createElement('div'); blockedDiv.classList.add('no-image-by-availability'); const lockIcon = document.createElement('i'); lockIcon.classList.add('bi', 'bi-lock', 'no-image-lock-icon'); const infoSpan = document.createElement('span'); infoSpan.classList.add('no-image-info'); blockedDiv.appendChild(lockIcon); blockedDiv.appendChild(infoSpan); newsTitle.classList.add('disabled-title'); const secondLockIcon = document.createElement('i'); secondLockIcon.classList.add('bi', 'bi-lock-fill', 'disabled-title-lock-icon'); titleAndArticle.prepend(secondLockIcon); newsTitle.textContent = "Backer-only content"; imageContainer.appendChild(blockedDiv); if (news.turning_point_type === undefined) { infoSpan.innerHTML = "Subscribe to the BACKER tier to unlock and read all news articles!"; newsTitle.textContent = "Backer-only content"; } else { infoSpan.innerHTML = "Subscribe to the INSIDER tier to read and DECIDE the outcome of turning points!"; newsTitle.textContent = "Insider-only content"; } const patreonButton = document.createElement('a'); patreonButton.classList.add('patreon-button'); patreonButton.href = "https://www.patreon.com/cw/f1dbeditor/membership"; //open in a new window patreonButton.target = "_blank"; const patreonIcon = document.createElement('div'); patreonIcon.classList.add('patreon-button-logo'); const patreonSpan = document.createElement('span'); patreonSpan.classList.add('patreon-button-text'); patreonSpan.textContent = "Support us on Patreon"; patreonButton.appendChild(patreonIcon); patreonButton.appendChild(patreonSpan); blockedDiv.appendChild(patreonButton); } addReadButtonListener(readButton, newsItem, news, newsList); contextButton.addEventListener('click', async () => { const result = await openContextModal(news.title); if (!result.confirmed) return; await openNewsModalFlow(news, newsItem, newsList, { extraContext: result.context }); }); newsItem.appendChild(imageContainer); titleAndArticle.appendChild(newsTitle); newsBody.appendChild(titleAndArticle); manageTurningPointButtons(news, newsList, maxDate, newsBody, readbuttonContainer, newsAvailable); newsBody.appendChild(readbuttonContainer); if (!news.nonReadable || news.nonReadable === false) { //first check - if the news is readable const canUserRead = (!isTurning && window.__USER_DATA__?.paidMember === true) || (isTurning && (window.__USER_DATA__?.tierNumber >= 2 || isAduoTurningPoint)); if (canUserRead) { if (canUseGenAiForNews(news)) { readActions.appendChild(contextButton); } readActions.appendChild(readButton); readbuttonContainer.appendChild(readActions); } } //if news has .isCurrentSeason and its false, remove read button and turning point buttons if (isCurrentSeason === false && (news.text === undefined || news.text === null)) { readButton.remove(); contextButton.remove(); const tpDiv = readbuttonContainer.querySelector('.turning-point-div'); if (tpDiv) tpDiv.remove(); } newsItem.appendChild(newsBody); if (news.type === "race_result" || news.type === "quali_result") { newsItem.dataset.type = news.type; } else if (news.type === "fake_transfer" || news.type === "big_transfer" || news.type === "contract_renewal" || news.type === "silly_season_rumors") { newsItem.dataset.type = "driver_transfers"; } else if (news.type === "massive_exit" || news.type === "massive_signing") { newsItem.dataset.type = "driver_transfers"; } else if (news.type === "potential_champion" || news.type === "world_champion" || news.type === "season_review" || news.type === "team_comparison" || news.type === "driver_comparison") { newsItem.dataset.type = "others"; } else if (news.type.includes("turning_point")) { newsItem.dataset.type = "turning_points"; } return newsItem; } function computeStableKey(n) { if (n.id != null && n.id !== "") return String(n.id); return "h:" + hashStr(`${n.title}|${n.date}`); } export async function place_news(newsAndTurningPoints, newsAvailable) { let newsList = newsAndTurningPoints.newsList; let turningPointState = newsAndTurningPoints.turningPointState; let isCurrentSeason = newsAndTurningPoints.isCurrentSeason; await finishGeneralLoader(); let maxDate; newsGrid.innerHTML = ''; for (let i = 0; i < newsList.length; i++) { const news = newsList[i]; // clave estable news.stableKey = news.stableKey ?? computeStableKey(news); const isTurning = ( news.turning_point_type === 'original' || news.turning_point_type === 'approved' || news.turning_point_type === 'cancelled' ); const isAduoTurningPoint = news.type === "turning_point_aduo"; const h = hashStr(news.stableKey); const isFreeTier = !window.__USER_DATA__?.paidMember && !newsAvailable.normal && !newsAvailable.turning; if (isFreeTier) { news.hiddenByAvailability = !isAduoTurningPoint; news.hiddenReason = news.hiddenByAvailability ? (isTurning ? 'turning' : 'normal') : null; } else { if (!newsAvailable.turning && isTurning) { news.hiddenByAvailability = (h % BUCKET_TURNING) !== 0; news.hiddenReason = news.hiddenByAvailability ? 'turning' : null; } else if (!newsAvailable.normal && !isTurning) { news.hiddenByAvailability = (h % BUCKET_NORMAL) !== 0; news.hiddenReason = news.hiddenByAvailability ? 'normal' : null; } else { news.hiddenByAvailability = false; news.hiddenReason = null; } } if (!maxDate || news.date > maxDate) maxDate = news.date; const newsItem = createNewsItemElement(news, i, newsAvailable, newsList, maxDate, isCurrentSeason); newsGrid.appendChild(newsItem); setTimeout(() => { newsItem.classList.remove('fade-in'); newsItem.style.removeProperty('--order'); newsItem.style.opacity = '1'; }, 1500); } if (!isCurrentSeason && isCurrentSeason !== undefined) { //if it's undefined it should go to else document.querySelector("#reloadNews").classList.add("d-none"); document.querySelector("#regenerateArticle").classList.add("d-none"); document.querySelector("#createCustomNews")?.classList.add("d-none"); } else { document.querySelector("#reloadNews").classList.remove("d-none"); document.querySelector("#regenerateArticle").classList.remove("d-none"); document.querySelector("#createCustomNews")?.classList.remove("d-none"); } } export async function place_turning_outcome(turningPointResponse, newsList) { if (!turningPointResponse) return; let saveName = getSaveName(); saveName = saveName.split('.')[0]; const newsItem = document.createElement('div'); newsItem.classList.add('news-item', 'fade-in'); const newsBody = document.createElement('div'); newsBody.classList.add('news-body'); const titleAndArticle = document.createElement('div'); titleAndArticle.classList.add('title-and-article'); const newsTitle = document.createElement('span'); newsTitle.classList.add('news-title', 'bold-font'); newsTitle.textContent = turningPointResponse.title; const imageContainer = document.createElement('div'); imageContainer.classList.add('news-image-container'); manage_overlay(imageContainer, turningPointResponse.overlay, turningPointResponse.data, turningPointResponse.image); const image = document.createElement('img'); image.classList.add('news-image'); image.setAttribute('data-src', turningPointResponse.image); image.src = turningPointResponse.image; image.setAttribute("loading", "lazy"); const readbuttonContainer = document.createElement('div'); readbuttonContainer.classList.add('read-button-container'); const readActions = document.createElement('div'); readActions.classList.add('read-actions'); const contextButton = document.createElement('div'); contextButton.classList.add('context-read-button'); contextButton.setAttribute('title', 'Add context'); const contextIcon = document.createElement('i'); contextIcon.classList.add('bi', 'bi-chat-text'); contextButton.appendChild(contextIcon); const readButton = document.createElement('div'); readButton.classList.add('read-button'); const readButtonSpan = document.createElement('span'); readButtonSpan.innerText = "Read"; readButton.appendChild(readButtonSpan); readButton.addEventListener('click', async () => { await openNewsModalFlow(turningPointResponse, newsItem, newsList); }); contextButton.addEventListener('click', async () => { const result = await openContextModal(turningPointResponse.title); if (!result.confirmed) return; await openNewsModalFlow(turningPointResponse, newsItem, newsList, { extraContext: result.context }); }); imageContainer.appendChild(image); newsItem.appendChild(imageContainer); titleAndArticle.appendChild(newsTitle); newsBody.appendChild(titleAndArticle); readActions.appendChild(contextButton); readActions.appendChild(readButton); readbuttonContainer.appendChild(readActions); newsBody.appendChild(readbuttonContainer); newsItem.appendChild(newsBody); //append it at the start of the news grid prependAnimated(newsGrid, newsItem, 250, 'cubic-bezier(.2,.8,.2,1)'); } async function imageExists(url) { try { const res = await fetch(url, { method: 'HEAD' }); return res.ok; } catch (err) { return false; } } function prependAnimated(container, newEl, duration = 250, easing = 'ease') { // 1) Hijos actuales y posiciones BEFORE const oldChildren = Array.from(container.children); const before = new Map(); oldChildren.forEach(el => before.set(el, el.getBoundingClientRect())); // 2) Inserta el nuevo al principio (todavía sin transición) container.insertBefore(newEl, container.firstChild); // 3) Forzamos reflow tras el insert (importante) // Leer una propiedad de layout obliga al navegador a calcular posiciones. // offsetHeight / getBoundingClientRect / getComputedStyle valen. void container.offsetHeight; // 4) Posiciones AFTER de los elementos que ya estaban const after = new Map(); oldChildren.forEach(el => after.set(el, el.getBoundingClientRect())); // 5) Preparar el NUEVO para entrar desde arriba (sin transición aún) const newRect = newEl.getBoundingClientRect(); newEl.style.willChange = 'transform, opacity'; newEl.style.transform = `translateY(-${newRect.height}px)`; newEl.style.opacity = '0'; // 6) Preparar los ANTIGUOS con el delta (sin transición aún) oldChildren.forEach(el => { const a = after.get(el); const b = before.get(el); if (!a || !b) return; const dx = b.left - a.left; const dy = b.top - a.top; if (dx === 0 && dy === 0) return; el.style.willChange = 'transform'; el.style.transform = `translate(${dx}px, ${dy}px)`; }); // 7) Forzamos reflow otra vez para “congelar” los transforms iniciales void container.offsetHeight; // 8) Activamos transición y “soltamos” a 0 para que animen newEl.style.transition = `transform ${duration}ms ${easing}, opacity ${duration}ms ${easing}`; oldChildren.forEach(el => { el.style.transition = `transform ${duration}ms ${easing}`; }); // Usar rAF ayuda a que el navegador separe bien los pasos requestAnimationFrame(() => { newEl.style.transform = 'translate(0, 0)'; newEl.style.opacity = '1'; oldChildren.forEach(el => { el.style.transform = 'translate(0, 0)'; }); }); // 9) Limpieza al terminar const clean = (el, prop) => (e) => { if (e.propertyName !== prop) return; el.style.transition = ''; el.style.transform = ''; el.style.willChange = ''; if (el === newEl) el.style.opacity = ''; el.removeEventListener('transitionend', cleanupFns.get(el)); cleanupFns.delete(el); }; const cleanupFns = new Map(); oldChildren.forEach(el => { const fn = clean(el, 'transform'); cleanupFns.set(el, fn); el.addEventListener('transitionend', fn); }); const newFn = clean(newEl, 'opacity'); cleanupFns.set(newEl, newFn); newEl.addEventListener('transitionend', newFn); } async function getTurningPointEvents(date) { const command = new Command("getNews", {}); let resp = await command.promiseExecute(); let news = resp.content; const newsWithId = Object.entries(news).map(([id, n]) => ({ id, ...n })); const turningPointsOutcomes = newsWithId.filter(n => n.id.startsWith('turning_point_outcome_') || n.id.includes('_world_champion')); const events = []; if (turningPointsOutcomes.length > 0) { turningPointsOutcomes.forEach(tp => { const turningDate = tp.date; if ((tp.turning_point_type === "positive" || tp.id.includes('_world_champion')) && Number(turningDate) <= Number(date)) { if (tp.id.includes("investment")) { events.push({ type: "investment", country: tp.data.country, amount: tp.data.investmentAmount, team: tp.data.teamName, share: tp.data.investmentShare }); } else if (tp.id.includes("technical_directive")) { events.push({ type: "technical_directive", component: tp.data.component, reason: tp.data.reason }); } else if (tp.id.includes("dsq")) { events.push({ type: "disqualification", country: tp.data.country, team: tp.data.team, component: tp.data.component }); } else if (tp.id.includes("substitution")) { events.push({ type: "race_substitution", original: tp.data.originalCountry, reason: tp.data.reason, substitute: tp.data.substituteCountry }); } else if (tp.id.includes("transfer")) { events.push({ type: "driver_transfer", driverOut: tp.data.driver_out?.name, team: tp.data.team, driverIn: tp.data.driver_in?.name }); } else if (tp.id.includes("injury")) { events.push({ type: "driver_injury", driver: tp.data.driver_affected?.name, condition: tp.data.condition?.condition, reason: tp.data.condition?.reason, racesMissed: tp.data.condition?.races_affected?.length || 1, replacement: tp.data.reserve_driver?.name }); } else if (tp.id.includes("_world_champion")) { events.push({ type: "world_champion_crowned", driver: tp.data.driver_name, team: combined_dict[tp.data.driver_team_id], season: tp.data.season_year, race: tp.data.adjective }); } } }); } return events; } function buildContextData(data, config = {}) { const { driverStandings, teamStandings, driversResults, racesNames, champions, driverQualiResults, enrichedAllTime } = data; const { timing = '', teamId = null, teamName = '', seasonYear = '' } = config; const contextData = { timing, seasonYear }; if (driverStandings) { contextData.driverStandings = driverStandings.map((d, i) => ({ position: i + 1, name: d.name, team: combined_dict[d.teamId], points: d.points, gapToLeader: d.gapToLeader || 0 })); } if (teamStandings) { contextData.teamStandings = teamStandings.map((t, i) => ({ position: i + 1, name: combined_dict[t.teamId] || `Team ${t.teamId}`, points: t.points })); } if (racesNames && racesNames.length > 0) { contextData.previousRaces = racesNames; } if (driversResults) { let resultsToProcess = driversResults; if (teamId) { resultsToProcess = driversResults.filter(d => d.teamId === teamId); } contextData.driverRaceResults = resultsToProcess.map(d => ({ name: d.name, wins: d.nWins, podiums: d.nPodiums, pointsFinishes: d.nPointsFinishes, resultsHistory: d.resultsString })); } if (driverQualiResults) { let resultsToProcess = driverQualiResults; if (teamId) { resultsToProcess = driverQualiResults.filter(d => d.teamId === teamId); } contextData.driverQualiResults = resultsToProcess.map(d => ({ name: d.name, wins: d.nWins, podiums: d.nPodiums, pointsFinishes: d.nPointsFinishes, resultsHistory: d.resultsString })); } if (champions) { contextData.championsHistory = Object.values( champions.reduce((acc, { season, pos, name, points }) => { if (!acc[season]) acc[season] = { season, drivers: [] }; acc[season].drivers.push({ position: pos, name, points }); return acc; }, {}) ).sort((a, b) => b.season - a.season); } if (enrichedAllTime && enrichedAllTime.length > 0) { let list = enrichedAllTime; if (teamId && 'teamId' in (list[0] || {})) { list = list.filter(d => d.teamId === teamId); } contextData.driverCareerStats = list.map(d => ({ name: d.name || `Driver ${d.id}`, championships: d.totalChampionshipWins ?? 0, wins: d.totalWins ?? 0, podiums: d.totalPodiums ?? 0, starts: d.totalStarts ?? 0, isRookie: Number(seasonYear) === Number(d.firstRace.season) })); } return contextData; } function buildContextualPrompt(data, config = {}) { const { driverStandings, teamStandings, driversResults, racesNames, champions, driverQualiResults, enrichedAllTime, driverRaceResults } = data; const { timing = '', teamId = null, teamName = '', seasonYear = '' } = config; let prompt = ''; if (driverStandings) { const driversChamp = driverStandings .map((d, i) => `${i + 1}. ${d.name} (${combined_dict[d.teamId]}) — ${d.points} pts (${d.gapToLeader ? `+${d.gapToLeader} pts to leader` : ''})`) .join("\n"); prompt += `\n\nCurrent Drivers' Championship standings ${timing}:\n${driversChamp}`; } if (teamStandings) { const teamsChamp = teamStandings .map((t, i) => { const name = combined_dict[t.teamId] || `Team ${t.teamId}`; return `${i + 1}. ${name} — ${t.points} pts`; }) .join("\n"); prompt += `\n\nCurrent Constructors' Championship standings ${timing}:\n${teamsChamp}`; } if (racesNames && racesNames.length > 0) { let previousRaces = racesNames.join(', '); prompt += `\n\nThe races that have already taken place before this one in ${seasonYear} are: ${previousRaces}\n`; } if (driversResults) { let resultsToProcess = driversResults; if (teamId) { resultsToProcess = driversResults.filter(d => d.teamId === teamId); } const previousResults = resultsToProcess.map((d) => { const details = [ d.nWins > 0 ? `${d.nWins} wins` : '', d.nPodiums > 0 ? `${d.nPodiums} podiums` : '', (d.nWins === 0 && d.nPodiums === 0 && d.nPointsFinishes > 0) ? `${d.nPointsFinishes} points finishes` : '' ].filter(Boolean).join(', '); return `${d.name}${details ? ` (${details})` : ''} ${d.resultsString}`; }).join("\n"); if (teamId && teamName) { prompt += `\n\nHere are the previous race results for ${teamName}'s drivers:\n${previousResults}`; } else if (resultsToProcess.length > 0) { prompt += `\n\nHere are the previous race results for each driver:\n${previousResults}`; } } if (driverQualiResults) { let qualiResultsToProcess = driverQualiResults; if (teamId) { qualiResultsToProcess = driverQualiResults.filter(d => d.teamId === teamId); } const previousQualiResults = qualiResultsToProcess.map(d => { const details = [ d.nWins > 0 ? `${d.nWins} poles` : '', d.nPodiums > 0 ? `${d.nPodiums} top 3s` : '', (d.nWins === 0 && d.nPodiums === 0 && d.nPointsFinishes > 0) ? `${d.nPointsFinishes} top 10s` : '' ].filter(Boolean).join(', '); return `${d.name}${details ? ` (${details})` : ''} ${d.resultsString}`; }).join("\n"); if (teamId && teamName) { prompt += `\n\nHere are the previous qualifying results for ${teamName}'s drivers:\n${previousQualiResults}`; } else if (qualiResultsToProcess.length > 0) { prompt += `\n\nHere are the previous qualifying results for each driver:\n${previousQualiResults}`; } } if (champions) { const previousChampions = Object.values( champions.reduce((acc, { season, pos, name, points }) => { if (!acc[season]) acc[season] = { season, drivers: [] }; acc[season].drivers.push(`${pos}. ${name} ${points}pts`); return acc; }, {}) ) .sort((a, b) => b.season - a.season) .map(({ season, drivers }) => `${season}\n${drivers.join('\n')}`) .join('\n\n'); prompt += `\n\nIf you want to mention that someone is the reigning champion, here are the last F1 world champions and runner ups:\n${previousChampions}`; } if (enrichedAllTime.length > 0) { let list = enrichedAllTime; // Si los objetos tienen teamId y hay filtro, aplícalo if (teamId && 'teamId' in (list[0] || {})) { list = list.filter(d => d.teamId === teamId); } // Construye las líneas: ": X titles, Y wins, Z podiums, W race starts" const lines = list.map(d => { const titles = d.totalChampionshipWins ?? 0; const wins = d.totalWins ?? 0; const podiums = d.totalPodiums ?? 0; const starts = d.totalStarts ?? 0; const name = d.name || `Driver ${d.id}`; const isRookie = Number(seasonYear) === Number(d.firstRace.season); const tLbl = titles === 1 ? 'drivers championship' : 'drivers championships'; const wLbl = wins === 1 ? 'race win' : 'race wins'; const pLbl = podiums === 1 ? 'podium' : 'podiums'; const sLbl = 'race starts'; return `${name} ${isRookie ? '(Rookie)' : ''}: ${titles} ${tLbl}, ${wins} ${wLbl}, ${podiums} ${pLbl}, ${starts} ${sLbl}`; }).join('\n'); if (lines) { prompt += `\n\nHere are the stats for each driver in the grid. Drivers not marked with (Rookie) are not rookies anymore. Only mention them if relevant:\n${lines}`; } } return prompt; } const ADUO_ENGINE_STAT_LABELS = { 10: "Power", 6: "Fuel efficiency", 14: "Engine durability", 18: "ERS durability", 19: "Gearbox durability" }; const ADUO_ENGINE_STAT_ORDER = [10, 6, 14, 18, 19]; function describeAduoChange(pct) { const v = Number(pct) || 0; if (v > 0) { if (v >= 7) return "significantly improved"; if (v >= 4) return "notably improved"; if (v >= 2) return "improved"; return "slightly improved"; } if (v < 0) { if (v <= -7) return "significantly worsened"; if (v <= -4) return "notably worsened"; if (v <= -2) return "worsened"; return "slightly worsened"; } return "unchanged"; } function buildAduoTemplateArticle(newData) { const quarterString = newData?.data?.quarterString || "current"; const seasonYear = newData?.data?.season; const engineImprovements = Array.isArray(newData?.data?.engineImprovements) ? newData.data.engineImprovements : []; const headerBits = ["## ADUO window"]; if (seasonYear != null) headerBits.push(`(${quarterString} quarter, season ${seasonYear})`); else headerBits.push(`(${quarterString} quarter)`); if (!engineImprovements.length) { return `${headerBits.join(" ")}\n\nNo manufacturer upgrade data is available for this window.\n\n---\n\n*Patreon members can generate a full AI-written article (with quotes and extra context) for this turning point.*`; } const blocks = engineImprovements.map((entry) => { const name = entry?.name || "Unknown manufacturer"; const improvements = entry?.improvements || {}; const lines = ADUO_ENGINE_STAT_ORDER.map((statId) => { const raw = improvements[statId] ?? improvements[String(statId)]; if (raw === undefined || raw === null) return null; const label = ADUO_ENGINE_STAT_LABELS[statId] || `Stat ${statId}`; return `- ${label}: ${describeAduoChange(raw)}`; }).filter(Boolean); return `**${name}**\n${lines.length ? lines.join("\n") : "- No detailed stat breakdown available."}`; }).join("\n\n"); return `${headerBits.join(" ")}\n\nThis turning point summary lists what each engine manufacturer is expected to improve or worsen in this ADUO window.\n\n${blocks}\n\n---\n\n*Patreon members can generate a full AI-written article (with quotes and extra context) for this turning point.*`; } async function manageRead(newData, newsList, barProgressDiv, interval, opts = {}) { const { force = false, extraContext = '' } = opts; const stableKey = newData.stableKey ?? newData.id ?? computeStableKey(newData); const normalizedType = newData?.type?.startsWith("turning_point_outcome_") ? newData.type.replace("turning_point_outcome_", "turning_point_") : newData?.type; if (normalizedType === "turning_point_aduo" && !canUseGenAiForNews(newData)) { clearInterval(interval); if (barProgressDiv) barProgressDiv.style.width = '100%'; return buildAduoTemplateArticle(newData); } // 1) Si ya hay texto y NO forzamos, devolvemos el existente if (newData.text && !force) { clearInterval(interval); if (barProgressDiv) barProgressDiv.style.width = '100%'; return newData.text; } // 2) Tabla de contextualizadores const ctx = { race_result: contextualizeRaceResults, quali_result: contextualizeQualiResults, fake_transfer: contextualizeFakeTransferNews, silly_season_rumors: contextualizeSillySeasonTransferNews, potential_champion: contextualizePotentialChampion, world_champion: contextualizeWorldChampion, big_transfer: contextualizeBigTransferConfirm, massive_exit: contextualizeBigTransferConfirm, massive_signing: contextualizeBigTransferConfirm, contract_renewal: contextualizeRenewalNews, team_comparison: contextualizeTeamComparison, driver_comparison: contextualizeDriverComparison, season_review: contextualizeSeasonReview, race_reaction: contextualizeRaceReaction, next_season_grid: contextualizeNextSeasonGrid, feeder_series_review: contextualizeFeederSeriesReview, custom_new: contextualizeCustomNews, // Turning points: outcome_ y no-outcome comparten handler turning_point_dsq: (nd) => contextualizeDSQ(nd, nd.turning_point_type), turning_point_transfer: (nd) => contextualizeTurningPointTransfer(nd, nd.turning_point_type), turning_point_technical_directive: (nd) => contextualizeTurningPointTechnicalDirective(nd, nd.turning_point_type), turning_point_investment: (nd) => contextualizeTurningPointInvestment(nd, nd.turning_point_type), turning_point_race_substitution: (nd) => contextualizeTurningPointRaceSubstitution(nd, nd.turning_point_type), turning_point_injury: (nd) => contextualizeTurningPointInjury(nd, nd.turning_point_type), turning_point_engine_regulation: (nd) => contextualizeTurningPointEngineRegulation(nd, nd.turning_point_type), turning_point_young_drivers: (nd) => contextualizeTurningPointYoungDrivers(nd, nd.turning_point_type), turning_point_aduo: (nd) => contextualizeTurningPointAduo(nd, nd.turning_point_type), }; // 3) Normaliza tipos "turning_point_outcome_*" -> "turning_point_*" const normalizeType = (t) => t?.startsWith("turning_point_outcome_") ? t.replace("turning_point_outcome_", "turning_point_") : t; const type = normalizeType(newData.type); const handler = ctx[type]; if (!handler) { console.warn("No handler for news type:", newData.type); } // 4) Progreso visual clearInterval(interval); // detenemos el anterior let progressInterval; try { if (barProgressDiv) barProgressDiv.style.width = '30%'; let progress = 30; progressInterval = setInterval(() => { progress = Math.min(progress + 1, 98); if (barProgressDiv) barProgressDiv.style.width = progress + '%'; if (progress >= 98) clearInterval(progressInterval); }, 450); // 5) Construir prompt SOLO si hace falta let messages = []; const selectedLanguage = getNewsLanguage(); const expectsJson = selectedLanguage !== DEFAULT_NEWS_LANGUAGE; if (handler) { let { instruction, context } = await handler(newData); const normalDate = excelToDate(newData.date); const isoDate = new Date( normalDate.getFullYear(), normalDate.getMonth(), normalDate.getDate() ).toISOString().split("T")[0]; // Add turning point events to context let date = newData.date; context = await addTurningPointContexts(context, date); // Add additional contextual info to the prompt template let finalInstruction = `The current date is ${isoDate}\n\n` + `\n\nAdd any quote you find apporpiate from the drivers or team principals if involved in the article. ` + `\n\nThe title of the article is: "${newData.title}"`; finalInstruction += `\n\nEvery time a name has (team name) after it, it means their team.\n\nUse **Markdown** formatting in your response for better readability:\n- Use "#" or "##" for main and secondary titles.\n- Always use **bold** driver names and important phrases.\n- ALWAYS use *italics* for quotes or emotional emphasis.\n- Use bullet points or numbered lists if needed. Do not include any raw HTML or code blocks.\nThe final output must be valid Markdown ready to render as HTML.\n`; if (expectsJson) { finalInstruction += `\n\nReturn ONLY a JSON object with exactly two keys: "title" and "body".` + ` The "title" must be the translated headline in ${selectedLanguage}.` + ` The "body" must be the full article in ${selectedLanguage} using Markdown.` + ` Do not include the title inside the body. Do not add extra keys or wrap the JSON in code fences.`; } finalInstruction = replaceLanguagePlaceholder(finalInstruction, selectedLanguage); instruction = replaceLanguagePlaceholder(instruction, selectedLanguage); // Message 1: Instruction messages.push({ role: "user", content: instruction }); if (extraContext.trim()) { const shortExtraContext = extraContext.slice(0, 1500); messages.push({ role: "user", content: `Additional context that you MUST incorporate:\n${shortExtraContext.trim()}` }); } // Message 1: Context Data messages.push({ role: "user", content: `Here is context about results, championship standings, driver stats and important events that happened throughout the season:\n\n${context}` }); // Message 3: Final instructions messages.push({ role: "user", content: finalInstruction }); // Ensure {{language}} placeholders are always substituted in every prompt message messages = messages.map(m => ({ ...m, content: replaceLanguagePlaceholder(m.content, selectedLanguage) })); } console.log("Final messages for AI:", messages); // 6) Llama a la IA y guarda const articleText = await askGenAI(messages); const parsedJson = tryParseJsonObject(articleText); let translatedTitle = ""; let cleanedArticleText = ""; if (parsedJson && typeof parsedJson.body === "string" && parsedJson.body.trim()) { cleanedArticleText = cleanArticleOutput(parsedJson.body); if (typeof parsedJson.title === "string") { translatedTitle = parsedJson.title.trim(); } } else { cleanedArticleText = cleanArticleOutput(articleText); } newData.text = cleanedArticleText; if (translatedTitle) { newData.title = translatedTitle; const modalTitle = document.querySelector('#newsModal .modal-title'); if (modalTitle) { modalTitle.textContent = translatedTitle; } const openedNewsTitle = document.querySelector('.news-item.opened .news-title'); if (openedNewsTitle) { openedNewsTitle.textContent = translatedTitle; } } updateRateLimitsDisplay(); const patch = { text: cleanedArticleText }; if (translatedTitle) { patch.title = translatedTitle; } new Command("updateNews", { stableKey, patch }).execute(); if (barProgressDiv) barProgressDiv.style.width = '100%'; return cleanedArticleText; } finally { if (progressInterval) clearInterval(progressInterval); } } function cleanArticleOutput(rawMd) { let md = rawMd || ""; md = removeLeading(md); md = italicizeQuotes(md); return md.trim(); } function safeJsonParse(raw) { try { return JSON.parse(raw); } catch { return null; } } function tryParseJsonObject(raw) { if (typeof raw !== "string") return null; let text = raw.trim(); if (!text) return null; if (text.startsWith("```")) { text = text .replace(/^```(?:json)?\s*/i, "") .replace(/```\s*$/i, "") .trim(); } let parsed = safeJsonParse(text); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { return parsed; } const first = text.indexOf("{"); const last = text.lastIndexOf("}"); if (first !== -1 && last !== -1 && last > first) { parsed = safeJsonParse(text.slice(first, last + 1)); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { return parsed; } } return null; } function removeLeading(md) { return md.replace(/^\s*#{1,3}\s.*\n+/, "").trimStart(); } function italicizeQuotes(md) { return md.replace(/"([^"]+)"/g, (match, inner, offset, full) => { const before = full[offset - 1] || ""; const after = full[offset + match.length] || ""; if (before === "*" || after === "*") { return match; } return `*${match}*`; }); } async function contextualizeCustomNews(newData) { const seasonYear = Number(newData?.data?.season_year || currentSeason || 0); const promptText = newData?.data?.prompt || "Write a custom Formula 1 article."; const imageFile = newData?.data?.image || ""; let prompt = `Write a Formula 1-style news article based on this custom request:\n${promptText}\n\n` + `The story must stay coherent with the save context, current standings, teams, and driver situation.\n` + `Do not mention raw filenames or that the story was AI-generated.`; if (imageFile) { prompt += `\n\nA supporting image has been selected for the article. Use it only as loose visual inspiration if it helps the tone.`; } const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details for custom news:", err); return { instruction: prompt, context: "" }; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointInjury(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 106); let prompt; let seasonYear = newData.data.season; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } prompt = prompt .replace(/{{\s*driver\s*}}/g, newData.data.driver_affected?.name || 'the driver') .replace(/{{\s*team\s*}}/g, newData.data.team || 'the team') .replace(/{{\s*condition\s*}}/g, newData.data.condition?.condition || 'a physical issue') .replace(/{{\s*reason\s*}}/g, newData.data.condition?.reason || 'a private medical matter') .replace(/{{\s*races_affected_count\s*}}/g, (newData.data.condition?.races_affected?.length || 1)) .replace(/{{\s*expectedReturnCountry\s*}}/g, newData.data.condition?.expectedReturnCountry || 'a later round'); if (newData.data.reserve_driver) { prompt = prompt.replace(/{{\s*reserve_driver_part\s*}}/g, () => { const reserveName = newData.data.reserve_driver?.name || 'the reserve driver'; const isFreeAgent = !!newData.data.reserve_driver?.isFreeAgent; const t = String(newData.data.turningPointType || newData.data.turning_point_type || '') .toLowerCase(); const isPositive = t.includes('positive'); const isNegative = t.includes('negative'); // Destinos y contexto const inTeam = newData.data.team || 'the team'; // equipo que sufre la baja (lo usabas arriba) const driverNameOut = newData.data.driver_affected?.name || 'the injured driver'; // piloto que se lesiona if (isFreeAgent) { if (isPositive) { return `${reserveName} will join ${inTeam} as a free agent to replace ${driverNameOut}.`; } else if (isNegative) { return `${reserveName} would have joined ${inTeam} as a free agent to replace ${driverNameOut}.`; } else { return `${reserveName} is being considered as a free-agent option to replace ${driverNameOut} at ${inTeam}.`; } } else { if (isPositive) { return `${reserveName} will be promoted from ${inTeam}'s reserve/academy to replace ${driverNameOut} at ${inTeam}.`; } else if (isNegative) { return `${reserveName} would have been promoted from ${inTeam}'s reserve/academy to replace ${driverNameOut} at ${inTeam}.`; } else { return `${reserveName} is being discussed as a promotion candidate from ${inTeam}'s reserve/academy to replace ${driverNameOut} at ${inTeam}.`; } } }); } const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointRaceSubstitution(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 105); let prompt; let seasonYear = newData.data.season; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } prompt = prompt.replace(/{{\s*original_race\s*}}/g, newData.data.originalCountry || 'The original race'). replace(/{{\s*substitute_race\s*}}/g, newData.data.substituteCountry || 'The substituted race'). replace(/{{\s*reason\s*}}/g, newData.data.reason || 'The reason'); const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointInvestment(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 102); let prompt; let seasonYear = newData.data.season; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } prompt = prompt.replace(/{{\s*team\s*}}/g, newData.data.team || 'The team'). replace(/{{\s*amount\s*}}/g, newData.data.investmentAmount || 'X'). replace(/{{\s*share\s*}}/g, newData.data.investmentShare || 'X'). replace(/{{\s*country\s*}}/g, newData.data.country || 'X'); const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeDSQ(newData, type) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 103); let prompt; let currentSeason = newData.data.currentSeason; let teamName = newData.data.team; let teamId = newData.data.teamId; if (type.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (type.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } prompt = prompt.replace(/{{\s*team\s*}}/g, newData.data.team || 'The team'). replace(/{{\s*adjective\s*}}/g, newData.data.adjective || 'current'). replace(/{{\s*component\s*}}/g, newData.data.component || 'floor'). replace(/{{\s*driver_1\s*}}/g, newData.data.driver_1.name || 'Driver 1'). replace(/{{\s*driver_2\s*}}/g, newData.data.driver_2.name || 'Driver 2'). replace(/{{\s*driver_1_pos\s*}}/g, newData.data.driver_1.position !== undefined ? newData.data.driver_1.position.toString() : 'X'). replace(/{{\s*driver_2_pos\s*}}/g, newData.data.driver_2.position !== undefined ? newData.data.driver_2.position.toString() : 'X'). replace(/{{\s*driver_1_points\s*}}/g, newData.data.driver_1.points !== undefined ? newData.data.driver_1.points.toString() : 'X'). replace(/{{\s*driver_2_points\s*}}/g, newData.data.driver_2.points !== undefined ? newData.data.driver_2.points.toString() : 'X'); const command = new Command("fullChampionshipDetailsRequest", { season: currentSeason, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const timing = type.includes("positive") ? "after the disqualification" : ""; const contextData = buildContextualPrompt(resp.content, { timing, teamId, teamName, seasonYear: currentSeason }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointTechnicalDirective(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 100); let prompt; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } let seasonYear = newData.data.season; const teams = newData.data.effectOnEachteam; //get the best 2 teams and worse 2 by performanceGainLoss. Each team is an object where the key is the teamId and it has an object that has teamName and performanceGainLoss const teamPerformances = Object.entries(teams).map(([teamId, { teamName, performanceGainLoss }]) => ({ teamId, teamName, performanceGainLoss })); const bestTeams = teamPerformances.sort((a, b) => b.performanceGainLoss - a.performanceGainLoss).slice(0, 2); const worstTeams = teamPerformances.sort((a, b) => a.performanceGainLoss - b.performanceGainLoss).slice(0, 2); prompt = prompt.replace(/{{\s*component\s*}}/g, newData.data.component || 'The component'). replace(/{{\s*best_team\s*}}/g, bestTeams[0]?.teamName || 'The team'). replace(/{{\s*second_best\s*}}/g, bestTeams[1]?.teamName || 'The team'). replace(/{{\s*worse_team\s*}}/g, worstTeams[0]?.teamName || 'The team'). replace(/{{\s*second_worse\s*}}/g, worstTeams[1]?.teamName || 'The team'). replace(/{{\s*reason\s*}}/g, newData.data.reason || 'The reason') const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointEngineRegulation(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 107); let prompt; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } const seasonYear = newData.data.season; const changeType = newData.data.changeType || "minor"; const changeArea = newData.data.mainChangeArea || "engine regulations"; const winners = Array.isArray(newData.data.winnerNames) ? newData.data.winnerNames : []; const losers = Array.isArray(newData.data.loserNames) ? newData.data.loserNames : []; const formatList = (list, fallback) => { if (!list.length) return fallback; if (list.length === 1) return list[0]; if (list.length === 2) return `${list[0]} and ${list[1]}`; return `${list.slice(0, -1).join(", ")} and ${list[list.length - 1]}`; }; const winnerNames = formatList(winners, "several manufacturers"); const loserNames = formatList(losers, "a few rivals"); prompt = prompt .replace(/{{\s*type\s*}}/g, changeType) .replace(/{{\s*change_area\s*}}/g, changeArea) .replace(/{{\s*winner_names\s*}}/g, winnerNames) .replace(/{{\s*loser_names\s*}}/g, loserNames); const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointAduo(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 109); let prompt = promptTemplateEntry?.prompt; if (!prompt) { console.warn("Missing turning point prompt template for ADUO (new_type 109)."); return; } const seasonYear = newData.data.season; const manufacturers = newData.data.manufacturers || "several manufacturers"; const numberPeriod = newData.data.quarterString || "current"; const engineImprovements = Array.isArray(newData.data.engineImprovements) ? newData.data.engineImprovements : []; const getBoostLabel = (pct) => { if (pct >= 7) return "a huge boost"; if (pct >= 4) return "a big boost"; if (pct >= 2) return "a solid gain"; if (pct > 0) return "a small increase"; if (pct === 0) return "little to no change"; if (pct > -1) return "a slight step backwards"; return "a noticeable drop"; }; const getAvgChange = (improvements) => { if (!improvements) return 0; let sum = 0; let count = 0; for (const statId of Object.keys(improvements)) { sum += Number(improvements[statId]) || 0; count += 1; } return count ? (sum / count) : 0; }; const getPowerChange = (improvements) => { if (!improvements) return null; if (improvements[10] !== undefined) return Number(improvements[10]) || 0; if (improvements["10"] !== undefined) return Number(improvements["10"]) || 0; return null; }; const upgradesSummary = engineImprovements.length ? engineImprovements.map(e => { const name = e.name || "Unknown manufacturer"; const improvements = e.improvements || {}; const avg = getAvgChange(improvements); const power = getPowerChange(improvements); if (power === null) { return `- ${name}: expected to make ${getBoostLabel(avg)} overall.`; } const powerLabel = getBoostLabel(power); const avgLabel = getBoostLabel(avg); if (powerLabel === avgLabel) { return `- ${name}: expected to make ${powerLabel} overall, including in power.`; } return `- ${name}: expected to get ${powerLabel} in power, with ${avgLabel} across the rest of the package.`; }).join("\n") : "No manufacturers were clearly identified for upgrades in this ADUO window."; prompt = prompt .replace(/{{\s*manufacturers\s*}}/g, manufacturers) .replace(/{{\s*number_period\s*}}/g, numberPeriod) .replace(/{{\s*upgrade_summary\s*}}/g, upgradesSummary); const engineAllocations = window.__ENGINE_ALLOCATIONS__; const engineNames = window.__ENGINE_NAMES__; if (engineAllocations && typeof engineAllocations === "object") { const teamIds = Object.keys(engineAllocations) .map((id) => Number(id)) // Hide "Team 32" when custom team is not enabled (renderer removes combined_dict[32] in that case). .filter((id) => id !== 32 || (32 in combined_dict)) .sort((a, b) => a - b); const lines = teamIds.map((teamId) => { const engineId = Number(engineAllocations[String(teamId)]); const teamName = combined_dict[teamId] || `Team ${teamId}`; const engineName = engineNames?.[engineId] || `Engine ${engineId}`; return `- ${teamName}: ${engineName}`; }); if (lines.length) { prompt += `\n\nCurrent engine allocations:\n${lines.join("\n")}`; } } const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointYoungDrivers(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 108); let prompt; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } const prospects = Array.isArray(newData.data.prospects) ? newData.data.prospects : []; const prospectsList = prospects.map(p => { const series = p.series || "Junior"; const age = p.age != null ? `age ${p.age}` : "age unknown"; const pos = p.position != null ? `P${p.position}` : ""; const points = p.points != null ? `${p.points} pts` : ""; return `${p.name} (${series}, ${age}, ${pos}, ${points})`; }).join("\n"); const seriesSet = new Set(prospects.map(p => p.series).filter(Boolean)); const seriesList = Array.from(seriesSet).filter(s => s !== "Free"); let seriesContext = ""; if (seriesSet.size === 0) { seriesContext = "There is no confirmed series breakdown for these prospects."; } else if (seriesSet.size === 1 && seriesSet.has("Free")) { seriesContext = "The standout names this year are drivers from regional formulas outside the main junior championships."; } else if (seriesSet.has("Free") && seriesList.length) { seriesContext = `The shortlist includes drivers from ${seriesList.join(" and ")} as well as drivers from regional formulas outside those championships.`; } else { seriesContext = `The shortlist includes drivers from ${seriesList.join(" and ")}.`; } prompt = prompt .replace(/{{\s*driver1\s*}}/g, newData.data.driver1 || "a leading prospect") .replace(/{{\s*driver2\s*}}/g, newData.data.driver2 || "another leading prospect") .replace(/{{\s*prospects_list\s*}}/g, prospectsList || "No prospect data available."); if (seriesContext) { prompt += `\n\n${seriesContext}`; } const seasonYear = newData.data.season; const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } let contextData = buildContextualPrompt(resp.content, { seasonYear }); if (prospectsList) { contextData += `\n\nYoung prospects summary:\n${prospectsList}`; } return { instruction: prompt, context: contextData }; } async function contextualizeTurningPointTransfer(newData, turningPointType) { const promptTemplateEntry = turningPointsTemplates.find(t => t.new_type === 101); let prompt; if (turningPointType.includes("positive")) { prompt = promptTemplateEntry.positive_prompt; } else if (turningPointType.includes("negative")) { prompt = promptTemplateEntry.negative_prompt; } else { prompt = promptTemplateEntry.prompt; } let seasonYear = newData.data.season; let driverInTeam = combined_dict[newData.data.driver_in.teamId] || 'the previous team'; prompt = prompt.replace(/{{\s*driver_in\s*}}/g, newData.data.driver_in.name || 'The driver'). replace(/{{\s*driver_out\s*}}/g, newData.data.driver_out.name || 'The driver'). replace(/{{\s*team\s*}}/g, newData.data.team || 'The team'). replace(/{{\s*driver_in_team\s*}}/g, driverInTeam || 'The previous team') let drivers = [ { driverId: newData.data.driver_in.id, teamId: newData.data.driver_in.teamId, name: newData.data.driver_in.name, team: combined_dict[newData.data.driver_in.teamId] } ] let date = newData.date; const command = new Command("transferRumorRequest", { drivers, date }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching transfer rumor:", err); return; } if (newData.data.driver_substitute && turningPointType.includes("positive")) { let driverSubstituteTeam = combined_dict[newData.data.driver_substitute.teamId] || ''; prompt = prompt.replace( /{{\s*driver_substitute_part\s*}}/g, () => { const substituteName = newData.data.driver_substitute.name; const driverInName = newData.data.driver_in.name; const inTeam = driverInTeam || 'the team'; let fromPart; if (driverSubstituteTeam) { if (driverSubstituteTeam === driverInTeam) { fromPart = `from the ${inTeam}'s academy program`; } else { fromPart = `from ${driverSubstituteTeam}`; } } else { fromPart = 'as a free agent'; } return `${substituteName} will sign for ${inTeam} as a substitute for ${driverInName}, coming ${fromPart}.`; } ); } else { prompt = prompt.replace(/{{\s*driver_substitute_part\s*}}/g, ''); } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeWorldChampion(newData) { const promptTemplateEntry = newsPromptsTemaplates.find(t => t.new_type === 9); if (!promptTemplateEntry) { console.error("Prompt template for new_type 8 not found!"); return "Error: Prompt template not found."; } let prompt = promptTemplateEntry.prompt; prompt = prompt.replace(/{{\s*driver_name\s*}}/g, newData.data.driver_name || 'The leading driver'); prompt = prompt.replace(/{{\s*season_year\s*}}/g, newData.data.season_year || 'the current season'); prompt = prompt.replace(/{{\s*circuit\s*}}/g, newData.data.circuit_name || 'the upcoming circuit'); prompt = prompt.replace(/{{\s*rival_driver_name\s*}}/g, newData.data.rival_driver_name || 'their closest rival'); prompt = prompt.replace(/{{\s*driver_points\s*}}/g, newData.data.driver_points !== undefined ? newData.data.driver_points.toString() : 'current'); prompt = prompt.replace(/{{\s*rival_points\s*}}/g, newData.data.rival_points !== undefined ? newData.data.rival_points.toString() : 'current'); const command = new Command("raceDetailsRequest", { raceid: newData.data.raceId, }); let standingsResp = ''; let contextData = ""; try { standingsResp = await command.promiseExecute(); if (standingsResp && standingsResp.content) { const raceNumber = standingsResp.content.racesNames.length + 1; const numberOfRace = `The title was sealed after race ${raceNumber} out of ${standingsResp.content.nRaces} in this season.`; prompt += `\n\n${numberOfRace}`; contextData = buildContextualPrompt(standingsResp.content, { timing: "after this race", seasonYear: newData.data.season_year }); } else { prompt += "\n\nCould not retrieve current championship standings."; } } catch (err) { console.error("Error fetching standings for potential champion news:", err); prompt += "\n\nError fetching championship standings."; } return { instruction: prompt, context: contextData }; } async function contextualizePotentialChampion(newData) { const promptTemplateEntry = newsPromptsTemaplates.find(t => t.new_type === 8); if (!promptTemplateEntry) { console.error("Prompt template for new_type 8 not found!"); return "Error: Prompt template not found."; } let prompt = promptTemplateEntry.prompt; prompt = prompt.replace(/{{\s*driver_name\s*}}/g, newData.data.driver_name || 'The leading driver'); prompt = prompt.replace(/{{\s*season_year\s*}}/g, newData.data.season_year || 'the current season'); prompt = prompt.replace(/{{\s*circuit\s*}}/g, newData.data.circuit_name || 'the upcoming circuit'); prompt = prompt.replace(/{{\s*rival_driver_name\s*}}/g, newData.data.rival_driver_name || 'their closest rival'); prompt = prompt.replace(/{{\s*driver_points\s*}}/g, newData.data.driver_points !== undefined ? newData.data.driver_points.toString() : 'current'); prompt = prompt.replace(/{{\s*rival_points\s*}}/g, newData.data.rival_points !== undefined ? newData.data.rival_points.toString() : 'current'); const command = new Command("raceDetailsRequest", { raceid: parseInt(newData.data.raceId) - 1, }); let standingsResp, previousRaces = ''; let contextData = ""; try { standingsResp = await command.promiseExecute(); if (standingsResp && standingsResp.content) { const c = standingsResp.content; const raceNumber = c.racesNames.length + 1; // Arregla el doble "sealed sealed" const numberOfRace = `The title could be sealed after race ${raceNumber} out of ${c.nRaces} this season.`; prompt += `\n\n${numberOfRace}`; // Contexto antes de esta carrera contextData = buildContextualPrompt(c, { timing: "before this race", seasonYear: newData.data.season_year }); // Reglas de puntos (tal cual las comunicas) prompt += `\n\nThe maximum amount of points for the winner in each race is ${c.pointsSchema.twoBiggestPoints[0]}. \nIn each sprint race the maximum points for the winner is 8. \n${c.pointsSchema.isLastraceDouble ? 'The last race of the season awards double points.' : ''} \n${c.pointsSchema.fastestLapBonusPoint ? 'There is also 1 bonus point for the fastest lap (top 10 finish required).' : ''} \n${c.pointsSchema.poleBonusPoint ? 'There is also 1 bonus point for pole position in qualifying.' : ''}`; // === NUEVO: carreras después de esta (quitamos la primera de remainingRaces) === const rem = Array.isArray(c.remainingRaces) ? c.remainingRaces : []; const afterThis = rem.length > 0 ? rem.slice(1) : []; if (afterThis.length > 0) { const racesText = afterThis .map(r => `${r.trackName}${r.sprint ? " (Sprint)" : ""}`) .join(", "); prompt += `\n\nAfter this race, there will be ${afterThis.length} more: ${racesText}.`; } else { prompt += `\n\nAfter this race, there will be no more races left.`; } // === NUEVO: cálculo de puntos máximos aún disponibles DESPUÉS de esta carrera === const schema = c.pointsSchema; const raceWinPts = Number(schema.twoBiggestPoints?.[0] ?? 25); // fallback 25 const sprintWinPts = 8; // según tu texto const flBonus = schema.fastestLapBonusPoint ? 1 : 0; const poleBonus = schema.poleBonusPoint ? 1 : 0; // Sumamos por cada carrera restante (afterThis) // Si isLastraceDouble, duplicamos SOLO los puntos de carrera de la última prueba. let maxRemaining = 0; for (let i = 0; i < afterThis.length; i++) { const r = afterThis[i]; const isLast = i === afterThis.length - 1; const baseRacePts = schema.isLastraceDouble && isLast ? raceWinPts * 2 : raceWinPts; const sprintPts = r.sprint ? sprintWinPts : 0; // Nota: asumimos que los bonus (pole/FL) NO se duplican aunque la última carrera tenga doble puntuación. maxRemaining += baseRacePts + sprintPts + flBonus + poleBonus; } prompt += `\n\nThe maximum number of points still available after this race is ${maxRemaining}.`; } else { prompt += "\n\nCould not retrieve current championship standings."; } } catch (err) { console.error("Error fetching standings for potential champion news:", err); prompt += "\n\nError fetching championship standings."; } return { instruction: prompt, context: contextData }; } async function contextualizeSillySeasonTransferNews(newData) { let season = newData.season; const date = newData.date || null; let prompt = newsPromptsTemaplates.find(t => t.new_type === 4).prompt; prompt = prompt.replace(/{{\s*season\s*}}/g, season); const command = new Command("transferRumorRequest", { drivers: newData.data.drivers, date: date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear: season }); return { instruction: prompt, context: contextData }; } async function contextualizeFakeTransferNews(newData) { let driverName = newData.data.drivers[0].name; let teamName = newData.data.drivers[0].team; const date = newData.date || null; let prompt = newsPromptsTemaplates.find(t => t.new_type === 7).prompt; prompt = prompt.replace(/{{\s*driver1\s*}}/g, driverName) .replace(/{{\s*team1\s*}}/g, teamName); const command = new Command("transferRumorRequest", { drivers: newData.data.drivers, date: date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } prompt += `\n\nHere are the transfers that you have to talk about:\n`; resp.content.driverMap.forEach((d) => { prompt += `${d.name} could be leaving ${d.actualTeam} ${d.potentialTeam ? " for " + d.potentialTeam : ""} ${d.potentialSalary ? "with an expected salary of around " + d.potentialSalary : ""}\n`; prompt += `\n\nHere are the previous results of ${d.actualTeam} in recent years:\n`; d.actualTeamPreviousResults.forEach((t) => { prompt += `${t.season} - ${getOrdinalSuffix(t.position)} ${t.points}pts\n`; }) prompt += `\n\nHere are the teams that ${d.name} has driven for in recent years:\n`; d.previouslyDrivenTeams.forEach((t) => { prompt += `${t.season} - ${t.teamName}\n`; }); }); const contextData = buildContextualPrompt(resp.content, { timing: "after the last race", seasonYear: resp.content.season }); return { instruction: prompt, context: contextData }; } async function contextualizeNextSeasonGrid(newData) { let season = newData.data.season_year; const date = newData.date || null; let prompt = newsPromptsTemaplates.find(t => t.new_type === 19).prompt; prompt = prompt.replace(/{{\s*season_year\s*}}/g, season); const command = new Command("fullChampionshipDetailsRequest", { season: season, date: date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } function buildTeamLineupSection(title, teams, driversKey) { return [ `\n\n${title}\n`, ...Object.values(teams).map(team => [ `\n\n**${team.name}**:\n\n`, team[driversKey].map(d => `- ${d.name}`).join('\n'), ].join('') ) ].join(''); } prompt += buildTeamLineupSection( `Here is the confirmed team lineup for each team for the next season (${season}):`, newData.data.teams, 'driversNextSeason' ); prompt += buildTeamLineupSection( `Here are the driver line ups for each team in the season that just ended (${season - 1}):`, newData.data.teams, 'driversThisSeason' ); const contextData = buildContextualPrompt(resp.content, { seasonYear: season - 1 }); return { instruction: prompt, context: contextData }; } async function contextualizeBigTransferConfirm(newData) { let driverName = newData.data.driver1 let potentialTeam = newData.data.team1 let originalTeam = newData.data.team2 const date = newData.date || null; let newType = 6; if (newData.type.includes("massive_exit")) { newType = 18; } let prompt = newsPromptsTemaplates.find(t => t.new_type === newType).prompt; prompt = prompt.replace(/{{\s*driver1\s*}}/g, driverName) .replace(/{{\s*team1\s*}}/g, potentialTeam) .replace(/{{\s*team2\s*}}/g, originalTeam); let drivers = [{ driverId: newData.data.driverId, name: driverName, team: originalTeam, teamId: newData.data.team2Id, previouslyDrivenTeams: newData.data.previouslyDrivenTeams, potentialSalary: newData.data.salary, potentialYearEnd: newData.data.endSeason }] const command = new Command("transferRumorRequest", { drivers: drivers, date: date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const confirmedTransfer = resp.content.driverMap.map(d => ({ driver: d.name, leavingTeam: d.actualTeam, joiningTeam: d.potentialTeam, salary: d.potentialSalary, currentTeamRecentHistory: d.actualTeamPreviousResults.map(t => ({ season: t.season, position: t.position, points: t.points })), driverHistory: d.previouslyDrivenTeams.map(t => ({ season: t.season, team: t.teamName })) })); const contextData = buildContextualPrompt(resp.content, { timing: "after this race", seasonYear: resp.content.season }); return { instruction: prompt, context: contextData }; } async function contextualizeRenewalNews(newData) { let driverName = newData.data.driver1 let potentialTeam = newData.data.team1 let originalTeam = newData.data.team2 let prompt = newsPromptsTemaplates.find(t => t.new_type === 10).prompt; prompt = prompt.replace(/{{\s*driver1\s*}}/g, driverName) .replace(/{{\s*team1\s*}}/g, potentialTeam); let drivers = [{ driverId: newData.data.driverId, name: driverName, team: originalTeam, teamId: newData.data.team2Id, previouslyDrivenTeams: newData.data.previouslyDrivenTeams, potentialSalary: newData.data.salary, potentialYearEnd: newData.data.endSeason }] const date = newData.date || null; const command = new Command("transferRumorRequest", { drivers: drivers, date: date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const contextData = buildContextualPrompt(resp.content, { timing: "after this race", seasonYear: resp.content.season }); return { instruction: prompt, context: contextData }; } async function contextualizeTeamComparison(newData) { let team1 = combined_dict[newData.data.team.teamId]; let seasonYear = newData.data.season; let compType = newData.data.compType; let ptsDif = Math.abs(newData.data.team.drop); let promptId; if (compType === "good") { promptId = 12 } else { promptId = 11 } let prompt = newsPromptsTemaplates.find(t => t.new_type === promptId).prompt; prompt = prompt.replace(/{{\s*team1\s*}}/g, team1) .replace(/{{\s*actualSeason\s*}}/g, seasonYear) .replace(/{{\s*lastSeason\s*}}/g, seasonYear - 1); const command = new Command("teamComparisonRequest", { team: newData.data.team.teamId, season: seasonYear, date: newData.date } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const currentContextData = { driverStandings: resp.content.currentDriverStandings, teamStandings: resp.content.currentTeamStandings, driversResults: resp.content.currentDriversResults, racesNames: resp.content.currentRacesNames, enrichedAllTime: resp.content.enrichedAllTime }; const contextData = buildContextualPrompt(currentContextData, { teamId: newData.data.team.teamId, teamName: team1, seasonYear }); const oldRacesNames = resp.content.oldRacesNames.join(', '); const oldSeasonResults = resp.content.oldDriversResults .filter(d => d.teamId === newData.data.team.teamId) .map(d => `${d.name} - ${d.resultsString}`) .join("\n"); prompt += `\n\nHere are the results from ${team1}'s drivers in ${seasonYear - 1}: ${oldRacesNames}\n`; prompt += `\n\n${oldSeasonResults}`; prompt += `\n\nHere are the previous results of ${team1} in recent years:\n`; resp.content.previousResultsTeam.forEach((t) => { if (t.season !== seasonYear) { prompt += `${t.season} - ${getOrdinalSuffix(t.position)} ${t.points}pts\n`; } }); return { instruction: prompt, context: contextData }; } async function contextualizeQualiResults(newData) { let poleName = newData.data.first; let seasonYear = newData.data.seasonYear let circuit = countries_data[races_names[parseInt(newData.data.trackId)]].country; let prompt = newsPromptsTemaplates.find(t => t.new_type === 1).prompt; prompt = prompt.replace(/{{\s*pole_driver\s*}}/g, poleName) .replace(/{{\s*season_year\s*}}/g, seasonYear) .replace(/{{\s*circuit\s*}}/g, circuit); const raceId = newData?.data?.raceId ?? Number.parseInt(newData?.id?.split("_")?.[2], 10); const command = new Command("qualiDetailsRequest", { raceid: raceId, } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const raceNumber = resp.content.racesNames.length + 1; const numberOfRace = `This was qualifying ${raceNumber} out of ${resp.content.nRaces} in this season.`; prompt += `\n\n${numberOfRace}`; const qualiResults = resp.content.details.map(row => ({ position: row.pos, name: row.name, team: combined_dict[row.teamId], gapToPole: row.gapToPole.toFixed(3), fastestLap: row.fastestLap })); prompt += `\n\nHere are the full qualifying results:\n`; const totalDrivers = qualiResults.length; qualiResults.forEach((result, index) => { const position = result.position; if (position === 11) { prompt += `\n-- Q2 --\n`; } if ( (totalDrivers <= 20 && position === 16) || (totalDrivers > 20 && position === 17) ) { prompt += `\n-- Q1 --\n`; } prompt += `${position}. ${result.name} (${result.team}) ${result.fastestLap}` + `${result.gapToPole > 0 ? ` +${result.gapToPole}s` : ''}\n`; }); const contextData = buildContextualPrompt(resp.content, { timing: "before this race", seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeRaceResults(newData) { let winnerName = newData.data.first; let seasonYear = newData.data.seasonYear let circuit = countries_data[races_names[parseInt(newData.data.trackId)]].country; let prompt = newsPromptsTemaplates.find(t => t.new_type === 2).prompt; prompt = prompt.replace(/{{\s*winner\s*}}/g, winnerName) .replace(/{{\s*season_year\s*}}/g, seasonYear) .replace(/{{\s*circuit\s*}}/g, circuit); const raceId = newData?.data?.raceId ?? Number.parseInt(newData?.id?.split("_")?.[2], 10); const command = new Command("raceDetailsRequest", { raceid: raceId, } ); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching race details:", err); return; } const raceNumber = resp.content.racesNames.length + 1; const numberOfRace = `This was race ${raceNumber} out of ${resp.content.nRaces} in this season.`; prompt += `\n\n${numberOfRace}`; const raceDetails = resp.content.details.map(row => ({ position: row.pos, name: row.name, team: combined_dict[row.teamId], startPos: row.grid, gap: row.gapToWinner > 0 ? `${Number(row.gapToWinner.toFixed(3))}s` : row.gapLaps > 0 ? `${row.gapLaps} laps` : ``, status: row.dnf !== 1 ? `+${row.points} pts` : 'DNF' })); prompt += `\n\nHere are the full race results:\n`; raceDetails.forEach(result => { prompt += `${result.position}. ${result.name} (${result.team}) | Grid P${result.startPos} | +${result.gap} | ${result.status}\n`; }); const safetyCars = resp.content.details[0].safetyCar; const virtualSafetyCars = resp.content.details[0].virtualSafetyCar; const safetyCarPhrase = `\n\nThere were ${safetyCars} safety car${safetyCars > 1 ? "s" : ""} and ${virtualSafetyCars} virtual safety car${virtualSafetyCars > 1 ? "s" : ""} during the race.` prompt += safetyCarPhrase; const top3 = resp.content.driverOfTheDayInfo; if (Array.isArray(top3) && top3.length > 0) { const first = top3[0]; const second = top3[1]; const third = top3[2]; const driverOfTheDayPhrase = ` \n\nThe Driver of the Day award went to ${first.name} (${combined_dict[first.teamId]}).\n${second ? `In second place was ${second.name} (${combined_dict[second.teamId]}),` : ''}${third ? ` followed by ${third.name} (${combined_dict[third.teamId]}).` : ''}\n\nWrite a paragraph analyzing why ${first.name.split(' ')[0]} might have received the award, and why the fans also voted for ${second ? second.name.split(' ')[0] : ''}${second && third ? ' and ' : ''}${third ? third.name.split(' ')[0] : ''}. `; prompt += driverOfTheDayPhrase; } if (resp.content.sprintDetails.length > 0) { prompt += `\n\nThere was a sprint race held on Saturday, which was won by ${resp.content.sprintDetails[0].name} (${combined_dict[resp.content.sprintDetails[0].teamId]}). Dedicate a paragraph discussing the sprint results`; } const contextData = buildContextualPrompt(resp.content, { timing: "after this race", seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeDriverComparison(newData) { let driver1 = newData.data.drivers[0].name; let driver2 = newData.data.drivers[1].name; let teamId = newData.data.teamId; let seasonYear = newData.data.season; let prompt = newsPromptsTemaplates.find(t => t.new_type === 13).prompt; prompt = prompt.replace(/{{\s*driver1\s*}}/g, driver1) .replace(/{{\s*driver2\s*}}/g, driver2) .replace(/{{\s*team1\s*}}/g, combined_dict[teamId]) .replace(/{{\s*actualSeason\s*}}/g, seasonYear) const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { teamId, teamName: combined_dict[teamId], seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeRaceReaction(newData) { let adjective = newData.data.adjective; let seasonYear = newData.data.seasonYear; let happyDriver = newData.data.randomHappyDriver.name; let unhappyDriver = newData.data.randomUnHappyDriver.name; let circuit = newData.data.circuit; let prompt = newsPromptsTemaplates.find(t => t.new_type === 16).prompt; prompt = prompt.replace(/{{\s*adjective\s*}}/g, adjective) .replace(/{{\s*season_year\s*}}/g, seasonYear) .replace(/{{\s*happy_driver\s*}}/g, happyDriver) .replace(/{{\s*unhappy_driver\s*}}/g, unhappyDriver) .replace(/{{\s*circuit\s*}}/g, circuit); const command = new Command("raceDetailsRequest", { raceid: newData.data.raceId, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const raceResults = resp.content.details.map(row => { const gapStr = row.gapToWinner > 0 ? `${Number(row.gapToWinner.toFixed(3))} seconds` : row.gapLaps > 0 ? `${row.gapLaps} laps` : `0 seconds`; return { position: row.pos, name: row.name, team: combined_dict[row.teamId], startPos: row.grid, gap: gapStr, status: row.dnf !== 1 ? `+${row.points} pts` : 'DNF' }; }); prompt += `\n\nHere are the race results:\n`; raceResults.forEach(r => { prompt += `${r.position}. ${r.name} (${r.team}) - Started: P${r.startPos}, Gap: ${r.gap}, Status: ${r.status}\n`; }); const top3 = resp.content.driverOfTheDayInfo; if (Array.isArray(top3) && top3.length > 0) { const first = top3[0]; const second = top3[1]; const third = top3[2]; const driverOfTheDayPhrase = ` \n\nThe Driver of the Day award went to ${first.name} (${combined_dict[first.teamId]}).\n${second ? `In second place was ${second.name} (${combined_dict[second.teamId]}),` : ''}${third ? ` followed by ${third.name} (${combined_dict[third.teamId]}).` : ''}\n\nWrite a paragraph analyzing why ${first.name.split(' ')[0]} might have received the award, and why the fans also voted for ${second ? second.name.split(' ')[0] : ''}${second && third ? ' and ' : ''}${third ? third.name.split(' ')[0] : ''}. `; prompt += driverOfTheDayPhrase; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); return { instruction: prompt, context: contextData }; } async function contextualizeFeederSeriesReview(newData) { let seasonYear = newData.data.season_year; const f2ChampionData = newData.data.f2_champion; const f3ChampionData = newData.data.f3_champion; let f2_champion = (typeof f2ChampionData === "string") ? f2ChampionData : f2ChampionData?.name; let f3_champion = (typeof f3ChampionData === "string") ? f3ChampionData : f3ChampionData?.name; let prompt = newsPromptsTemaplates.find(t => t.new_type === 20).prompt; prompt = prompt.replace(/{{\s*season_year\s*}}/g, seasonYear) .replace(/{{\s*f2_champion\s*}}/g, f2_champion) .replace(/{{\s*f3_champion\s*}}/g, f3_champion); const command = new Command("fullFeederSeriesDetailsRequest", { season: seasonYear }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return { instruction: prompt, context: "" }; } const f2Context = buildContextualPrompt({ enrichedAllTime: [], ...(resp.content?.f2 || {}) }, { seasonYear, timing: "(Formula 2)" }); const f3Context = buildContextualPrompt({ enrichedAllTime: [], ...(resp.content?.f3 || {}) }, { seasonYear, timing: "(Formula 3)" }); const contextData = `For Formula 2 and Formula 3, each race weekend includes a Sprint and a Main race.\n` + `In the driver results strings, the format is: Main result (SPR Sprint result).\n` + `\n\n=== Formula 2 ===\n${f2Context}\n\n=== Formula 3 ===\n${f3Context}`; return { instruction: prompt, context: contextData }; } async function contextualizeSeasonReview(newData) { let seasonYear = newData.data.season; let driver1 = newData.data.firstDriver.name; let driver2 = newData.data.secondDriver.name; let team1 = combined_dict[newData.data.firstTeam.teamId]; let team2 = combined_dict[newData.data.secondTeam.teamId]; let part = newData.data.part; let newType = part === 3 ? 15 : 14; let prompt = newsPromptsTemaplates.find(t => t.new_type === newType).prompt; prompt = prompt.replace(/{{\s*season_year\s*}}/g, seasonYear) .replace(/{{\s*part\s*}}/g, part) .replace(/{{\s*driver1\s*}}/g, driver1) .replace(/{{\s*driver2\s*}}/g, driver2) .replace(/{{\s*team1\s*}}/g, team1) .replace(/{{\s*team2\s*}}/g, team2); const command = new Command("fullChampionshipDetailsRequest", { season: seasonYear, }); let resp; try { resp = await command.promiseExecute(); } catch (err) { console.error("Error fetching full championship details:", err); return; } const contextData = buildContextualPrompt(resp.content, { seasonYear }); const carPerformanceStart = Object.entries(resp.content.carsPerformance[0]) .sort((a, b) => b[1] - a[1]) .map(([team, value]) => `${team}: ${value.toFixed(2)}`) .join("\n"); const carPerformanceEnd = Object.entries(resp.content.carsPerformance[resp.content.carsPerformance.length - 1]) .sort((a, b) => b[1] - a[1]) .map(([team, value]) => `${team}: ${value.toFixed(2)}`) .join("\n"); prompt += `\n\nThe performance of each car is measured on a scale from 0 to 100, where 100 represents the ideal possible performance. A higher value indicates a better-performing car. The performance values should be taken with a grain of salt, as they are estimates, but they are very important to understand drivers and teams performance. Never ever mention the numbers given as they are only an estimate rather than an absolute truth and are only there to put into numbers the performance of each car. You can mention if a car is performing well or poorly, or how has it evolved but never give the exact numbers.\n`; prompt += `\n\nHere is the performance of each car at the start of the season:\n${carPerformanceStart}`; prompt += `\n\nHere is the performance of each car at the latest race:\n${carPerformanceEnd}`; return { instruction: prompt, context: contextData }; } async function askGenAI(messages, opts = {}) { const response = await fetch("/api/ask-openai", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ messages, max_tokens: opts.max_tokens || 4000 }) }); let data = {}; try { data = await response.json(); } catch { // ignore JSON parse errors } if (!response.ok) { const error = new Error(data.error || "AI request failed"); error.status = response.status; throw error; } return data.text; } newsOptionsBtn.addEventListener("click", (e) => { const btn = e.currentTarget; btn.classList.toggle("active"); if (!btn.classList.contains('active')) { setOptionsContextOpen(false); } }); deleteArticleBtn.addEventListener("click", async () => { const articleId = document.querySelector("#newsModal").getAttribute("data-article-id"); const command = new Command("deleteNewsArticle", { articleId }); command.promiseExecute().then(() => { const openedNewsItem = document.querySelector('.news-item.opened'); if (openedNewsItem) { openedNewsItem.remove(); } closeBtn?.click(); }); }); copyArticleBtn.addEventListener("click", async () => { const titleEl = document.querySelector("#newsModalTitle"); const articleEl = document.querySelector("#newsModal .news-article"); if (!titleEl || !articleEl) return; const title = titleEl.innerText.trim(); const turndownService = new TurndownService({ headingStyle: "atx", bulletListMarker: "-", codeBlockStyle: "fenced", }); const articleMarkdown = turndownService.turndown(articleEl.innerHTML); const finalText = `# ${title}\n\n${articleMarkdown}`; await navigator.clipboard.writeText(finalText); }); function createEditFooterButtons(articleEl) { const buttonsWrapper = closeBtn?.parentElement; if (!buttonsWrapper) return; closeBtn.classList.add('d-none'); saveArticleBtn = document.createElement('button'); saveArticleBtn.type = 'button'; saveArticleBtn.classList.add('confirm-modal'); saveArticleBtn.textContent = 'Save'; cancelArticleBtn = document.createElement('button'); cancelArticleBtn.type = 'button'; cancelArticleBtn.classList.add('close-modal'); cancelArticleBtn.textContent = 'Cancel'; buttonsWrapper.appendChild(cancelArticleBtn); buttonsWrapper.appendChild(saveArticleBtn); cancelArticleBtn.addEventListener('click', () => exitArticleEditMode()); saveArticleBtn.addEventListener('click', async () => { if (!editTextarea || !editTitleInput) return; const markdownText = editTextarea.value.trim(); const newTitle = editTitleInput.value.trim(); const parsedHtml = marked.parse(markdownText); const safeHtml = DOMPurify.sanitize(parsedHtml); const modalTitle = document.querySelector('#newsModal .modal-title'); articleEl.innerHTML = safeHtml; if (modalTitle && newTitle) { modalTitle.textContent = newTitle; } if (currentModalNews) { if (newTitle) { currentModalNews.title = newTitle; } currentModalNews.text = markdownText; new Command("updateNews", { stableKey: currentModalNews.id ?? computeStableKey(currentModalNews), patch: { text: markdownText, title: newTitle || currentModalNews.title } }).execute(); const openedNewsTitle = document.querySelector('.news-item.opened .news-title'); if (openedNewsTitle && newTitle) { openedNewsTitle.textContent = newTitle; } } else { new Command("updateNews", { stableKey: computeStableKey({ title: newTitle, date: null }), patch: { text: markdownText, title: newTitle } }).execute(); } exitArticleEditMode({ restoreOriginal: false }); originalArticleHTML = articleEl.innerHTML; }); } function startArticleEditMode() { if (isEditingArticle) return; const articleEl = document.querySelector("#newsModal .news-article"); if (!articleEl || !currentModalNews) return; newsOptionsBtn?.classList.remove('active'); const turndownService = new TurndownService({ headingStyle: "atx", bulletListMarker: "-", codeBlockStyle: "fenced", }); const markdownText = turndownService.turndown( articleEl.innerHTML || currentModalNews.text || '' ); const modalTitle = document.querySelector('#newsModal .modal-title'); const currentTitle = modalTitle?.textContent?.trim() || currentModalNews.title || ''; originalTitleText = currentTitle; originalArticleHTML = articleEl.innerHTML; articleEl.innerHTML = ''; const actualTitle = document.querySelector("#newsModalTitle"); editTitleInput = document.createElement('textarea'); editTitleInput.rows = 1; editTitleInput.classList.add('news-edit-title'); editTitleInput.value = currentTitle; actualTitle.innerHTML = ''; actualTitle.appendChild(editTitleInput); //get width of newsModal .modal-header const modalHeader = document.querySelector('#newsModal .modal-header'); if (modalHeader) { editTitleInput.style.maxWidth = (modalHeader.clientWidth - 60) + 'px'; } editTextarea = document.createElement('textarea'); editTextarea.classList.add('news-edit-textarea'); editTextarea.value = markdownText; articleEl.appendChild(editTextarea); isEditingArticle = true; createEditFooterButtons(articleEl); } editArticleBtn?.addEventListener("click", startArticleEditMode); function buildEmergencyOverlay() { const overlayDiv = document.createElement('div'); overlayDiv.classList.add('breaking-news-overlay', 'bold-font'); const breakingSpan = document.createElement('span'); breakingSpan.classList.add('breaking-news-breaking'); breakingSpan.innerText = 'BREAKING'; overlayDiv.appendChild(breakingSpan); const newsSpan = document.createElement('span'); newsSpan.classList.add('breaking-news-news'); newsSpan.innerText = 'NEWS'; overlayDiv.appendChild(newsSpan); const bar = document.createElement('div'); bar.classList.add('breaking-news-bar'); overlayDiv.appendChild(bar); document.body.appendChild(overlayDiv); return overlayDiv; } function ensureEmergencyOverlay(imageContainer) { if (!imageContainer.querySelector('.breaking-news-overlay')) { imageContainer.prepend(buildEmergencyOverlay()); } } function manage_overlay(imageContainer, overlay, data, image) { let overlayDiv = null; const teamColorForId = (teamId, fallback = '#ffffff') => { const candidate = Number.parseInt(teamId, 10); return colors_dict?.[`${candidate}0`] ?? fallback; }; const buildCenteredOverlay = ({ overlayClass, title, subtitle, borderColor, subtitleClass } = {}) => { const centeredOverlay = document.createElement('div'); centeredOverlay.classList.add('news-centered-overlay'); if (overlayClass) centeredOverlay.classList.add(overlayClass); const blockDiv = document.createElement('div'); blockDiv.classList.add('news-centered-block'); if (borderColor) blockDiv.style.borderBottomColor = borderColor; const titleDiv = document.createElement('div'); titleDiv.classList.add('news-centered-title'); titleDiv.innerText = title ?? ''; const subtitleDiv = document.createElement('div'); subtitleDiv.classList.add('news-centered-subtitle'); if (subtitleClass) subtitleDiv.classList.add(subtitleClass); subtitleDiv.innerText = subtitle ?? ''; blockDiv.appendChild(titleDiv); if (subtitleDiv.innerText) blockDiv.appendChild(subtitleDiv); centeredOverlay.appendChild(blockDiv); return centeredOverlay; }; const pickRandom = (arr) => { if (!Array.isArray(arr) || arr.length === 0) return ''; return arr[Math.floor(Math.random() * arr.length)]; }; const pickUnique = (arr, count) => { const list = Array.isArray(arr) ? [...arr] : []; for (let i = list.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [list[i], list[j]] = [list[j], list[i]]; } return list.slice(0, Math.max(0, count)); }; if (overlay === "race-overlay" || overlay === "quali-overlay") { overlayDiv = document.createElement('div'); overlayDiv.classList.add('race-overlay'); const first = document.createElement('div'); const firstTeam = document.createElement('div'); firstTeam.classList.add('position-team', 'firstpos'); firstTeam.innerText = combined_dict[data.firstTeam]; let borderClass = team_dict[data.firstTeam] + "border-top"; first.classList.add('position', 'firstpos', borderClass); let numberSpan = document.createElement("span"); numberSpan.className = "new-number"; numberSpan.textContent = "1."; //take only the lastname if there are multiple names let textNode = document.createTextNode(` ${data.first.split(" ").pop()}`); first.appendChild(numberSpan); first.appendChild(textNode); const second = document.createElement('div'); const secondTeam = document.createElement('div'); secondTeam.classList.add('position-team'); secondTeam.innerText = combined_dict[data.secondTeam]; borderClass = team_dict[data.secondTeam] + "border-top"; second.classList.add('position', borderClass); numberSpan = document.createElement("span"); numberSpan.className = "new-number"; numberSpan.textContent = "2."; textNode = document.createTextNode(` ${data.second.split(" ").pop()}`); second.appendChild(numberSpan); second.appendChild(textNode); const third = document.createElement('div'); const thirdTeam = document.createElement('div'); thirdTeam.classList.add('position-team'); thirdTeam.innerText = combined_dict[data.thirdTeam]; borderClass = team_dict[data.thirdTeam] + "border-top"; third.classList.add('position', borderClass); numberSpan = document.createElement("span"); numberSpan.className = "new-number"; numberSpan.textContent = "3."; textNode = document.createTextNode(` ${data.third.split(" ").pop()}`); third.appendChild(numberSpan); third.appendChild(textNode); const sessionDiv = document.createElement('div'); sessionDiv.classList.add('session'); sessionDiv.innerText = overlay === "race-overlay" ? "Race" : "Qualifying"; overlayDiv.appendChild(sessionDiv); overlayDiv.appendChild(first); overlayDiv.appendChild(firstTeam); overlayDiv.appendChild(second); overlayDiv.appendChild(secondTeam); overlayDiv.appendChild(third); overlayDiv.appendChild(thirdTeam); imageContainer.appendChild(overlayDiv); } else if (overlay === "reaction-overlay") { overlayDiv = document.createElement('div'); overlayDiv.classList.add('reaction-overlay'); const candidateTeamId = Number.parseInt(data?.driverTeamIdInTitle, 10); const teamId = candidateTeamId >= 1 && candidateTeamId <= 10 ? candidateTeamId : (Math.floor(Math.random() * 10) + 1); const teamColor = colors_dict?.[`${teamId}0`] ?? '#ffffff'; const titleDiv = document.createElement('div'); titleDiv.classList.add('reaction-title'); titleDiv.style.borderBottomColor = teamColor; const titleTextDiv = document.createElement('div'); titleTextDiv.classList.add('reaction-title-text'); titleTextDiv.innerText = 'POST RACE REACTIONS'; const raceNameDiv = document.createElement('div'); const raceGP = countries_data?.[races_names?.[data?.trackId]]?.adjective ?? 'Unknown GP'; raceNameDiv.classList.add('reaction-race-name'); raceNameDiv.innerText = `${data?.seasonYear ?? ''} ${raceGP} GP`.trim(); titleDiv.appendChild(titleTextDiv); titleDiv.appendChild(raceNameDiv); overlayDiv.appendChild(titleDiv); imageContainer.appendChild(overlayDiv); } else if (overlay === "fake-transfer-overlay") { const driver = data?.drivers?.[0]; const driverName = driver?.name ?? ''; const teamName = driver?.team ?? 'their current team'; const teamId = driver?.teamId; const phrases = [ `Could be leaving ${teamName}`, `Rumours of a departure from ${teamName}`, `Exit talks at ${teamName}`, `A possible move away from ${teamName}`, `${teamName} future uncertain`, `Departure from ${teamName} speculated`, `Linked with a move away from ${teamName}`, `Considering options beyond ${teamName}`, `In talks to leave ${teamName}`, `His manager weighing up options beyond ${teamName}` ]; const borderColor = teamColorForId(teamId, '#ffffff'); overlayDiv = buildCenteredOverlay({ overlayClass: 'fake-transfer-overlay', title: driverName, subtitle: pickRandom(phrases), borderColor }); imageContainer.appendChild(overlayDiv); } else if (overlay === "silly-season-overlay") { const drivers = Array.isArray(data?.drivers) ? data.drivers : []; const surnames = drivers .map(d => (typeof d?.name === 'string' ? d.name.trim().split(/\s+/).pop() : '')) .filter(Boolean); const shown = pickUnique(surnames, 3); const subtitle = shown.length ? shown.join(' • ') : ''; const candidateTeamId = drivers.find(d => d?.teamId != null)?.teamId; const borderColor = teamColorForId(candidateTeamId ?? (Math.floor(Math.random() * 10) + 1), '#ffffff'); overlayDiv = buildCenteredOverlay({ overlayClass: 'silly-season-overlay', title: 'SILLY SEASON', subtitle, subtitleClass: 'silly-season-names', borderColor }); imageContainer.appendChild(overlayDiv); } else if (overlay === "contract-renewal-overlay") { const driverName = data?.driver1 ?? ''; const teamName = data?.team1 ?? ''; const teamId = data?.team1Id; const phrases = [ `Stays with ${teamName}`, `Renews with ${teamName}`, `Extends deal at ${teamName}`, `Signs a new contract with ${teamName}`, `Commits future to ${teamName}` ]; const borderColor = teamColorForId(teamId, '#ffffff'); overlayDiv = buildCenteredOverlay({ overlayClass: 'contract-renewal-overlay', title: driverName, subtitle: pickRandom(phrases.filter(p => !p.endsWith(' ') && p.trim())), borderColor }); imageContainer.appendChild(overlayDiv); } else if (overlay === "massive-exit-overlay") { const driverName = data?.driver1 ?? ''; const teamName = data?.team1 ?? ''; const teamId = data?.team1Id; const headlines = [ 'CONFIRMED BREAKING', 'BREAKING CONFIRMED', 'OFFICIAL BREAKING', 'CONFIRMED BREAKING', 'OFFICIAL ANNOUNCEMENT', "HERE WE GO", "IT'S OFFICIAL", "EXCLUUSIVE BREAKING", "BREAKING NEWS", ]; const borderColor = teamColorForId(teamId, '#ffffff'); overlayDiv = buildCenteredOverlay({ overlayClass: 'massive-exit-overlay', title: pickRandom(headlines), subtitle: `${driverName} leaving ${teamName}`.trim(), borderColor }); imageContainer.appendChild(overlayDiv); } else if (overlay === "massive-signing-overlay") { const driverName = data?.driver1 ?? ''; const teamName = data?.team2 ?? ''; const teamId = data?.team2Id; const headlines = [ 'CONFIRMED BREAKING', 'BREAKING CONFIRMED', 'OFFICIAL BREAKING', 'CONFIRMED BREAKING', 'OFFICIAL ANNOUNCEMENT', "HERE WE GO", "IT'S OFFICIAL", "EXCLUUSIVE BREAKING", "BREAKING NEWS" ]; const borderColor = teamColorForId(teamId, '#ffffff'); overlayDiv = buildCenteredOverlay({ overlayClass: 'massive-signing-overlay', title: pickRandom(headlines), subtitle: `${driverName} signing with ${teamName}`.trim(), borderColor }); imageContainer.appendChild(overlayDiv); } else if (overlay === "driver-comparison-overlay") { const teamId = data.teamId; overlayDiv = document.createElement('div'); overlayDiv.classList.add('driver-comparison-overlay'); const teamColor = colors_dict[teamId + "0"] ?? '#000000'; let contrastColor; if (lightColors.includes(teamColor)) { contrastColor = "#272727"; } else { contrastColor = "#eeeef1"; } let gradientColor = `color-mix(in srgb, ${teamColor} 30%, ${contrastColor})`; const rareLogosTeams = [5, 8, 9]; if (rareLogosTeams.includes(teamId)) { overlayDiv.style.background = `linear-gradient(to top right, ${gradientColor} 0%, ${teamColor} 25%, ${teamColor} 75%, ${gradientColor} 100%)`; const textDiv = document.createElement('div'); textDiv.classList.add('new-team-text', 'bold-font'); if (lightColors.indexOf(teamColor) !== -1) { textDiv.style.color = "#272727"; } else { textDiv.style.color = '#eeeef1'; } const teamName = document.createElement('div'); teamName.classList.add('new-team-name'); teamName.innerText = combined_dict[teamId]; const driversDiv = document.createElement('div'); driversDiv.classList.add('new-drivers-names'); driversDiv.innerHTML = `${data.drivers[0].name} vs ${data.drivers[1].name}`; textDiv.appendChild(teamName); textDiv.appendChild(driversDiv); driversDiv.querySelector(".new-vs").style.backgroundColor = teamColor; overlayDiv.appendChild(textDiv); } else { overlayDiv.style.background = teamColor; let value = logos_disc[teamId]; if (teamId === 2 || teamId === 6) { value = value.replace(".png", "2.png"); } const logoImg = document.createElement('img'); logoImg.src = value ?? ''; logoImg.dataset.src = value ?? ''; logoImg.classList.add('new-team-logo'); overlayDiv.appendChild(logoImg); } imageContainer.appendChild(overlayDiv); } try { const url = (image instanceof HTMLImageElement) ? (image.currentSrc || image.src) : (typeof image === 'string' ? image : null); if (!url) { ensureEmergencyOverlay(imageContainer); return; } const probe = new Image(); probe.onload = () => { /* ok, no hacemos nada */ }; probe.onerror = () => { ensureEmergencyOverlay(imageContainer); }; probe.src = url; } catch { console.warn('Image probe failed unexpectedly'); } } function getOrdinalSuffix(n) { let j = n % 10, k = n % 100; if (j == 1 && k != 11) { return n + "st"; } if (j == 2 && k != 12) { return n + "nd"; } if (j == 3 && k != 13) { return n + "rd"; } return n + "th"; } setupNewsLanguageDropdown(); document.querySelectorAll('#newsTypeMenu .redesigned-dropdown-item').forEach(item => { item.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); item.classList.toggle('inactive'); // sincroniza el icono item.querySelector('i').classList.toggle('unactive', item.classList.contains('inactive')); const type = item.dataset.value; const hide = item.classList.contains('inactive'); document.querySelectorAll(`.news-item[data-type="${type}"]`).forEach(n => { n.style.display = hide ? 'none' : ''; }); }); }); document.querySelector("#reloadNews").addEventListener("click", async () => { const newsGrid = document.querySelector(".news-grid"); newsGrid.innerHTML = ''; const command = new Command("deleteNews"); command.execute(); generateNews(); }); const CUSTOM_NEWS_TYPE_DEFS = Object.entries(CUSTOM_NEWS_TYPE_META).map(([value, meta]) => ({ value, label: meta.label, group: meta.group || "normal" })); let customNewsModal = null; let customNewsOptionsCache = null; function setCustomNewsError(msg) { if (!customNewsError) return; if (msg) { customNewsError.textContent = msg; customNewsError.classList.remove('custom-news-hidden'); } else { customNewsError.textContent = ''; customNewsError.classList.add('custom-news-hidden'); } } function getCustomNewsTitlePlaceholder(type = getCustomNewsSelectedType()) { return type === "custom_new" ? "Write the article title" : "Automatic / default title"; } function getCustomNewsImageSrc(imageFile) { return imageFile ? `../assets/images/news/${imageFile}` : ""; } function toIsoDate(date) { const d = new Date(date.getFullYear(), date.getMonth(), date.getDate()); return d.toISOString().slice(0, 10); } function setRedesignedDropdownOpen(dropdown, isOpen) { if (!dropdown) return; dropdown.classList.toggle("open", !!isOpen); dropdown.setAttribute("aria-expanded", isOpen ? "true" : "false"); } function bindRedesignedDropdownToggle(dropdown) { if (!dropdown || dropdown._customDropdownBound) return; dropdown._customDropdownBound = true; dropdown.addEventListener("click", function (e) { if (dropdown.disabled) return; e.stopPropagation(); document.querySelectorAll(".redesigned-dropdown.open").forEach(openDropdown => { if (openDropdown !== dropdown) { setRedesignedDropdownOpen(openDropdown, false); } }); setRedesignedDropdownOpen(dropdown, !dropdown.classList.contains("open")); }); } function getDropdownLabelSpan(buttonEl) { return buttonEl?.querySelector(".dropdown-label") || buttonEl?.querySelector("span") || null; } function setRedesignedDropdownSelection(buttonEl, menuEl, value, label) { if (!buttonEl) return; buttonEl.dataset.value = value == null ? "" : String(value); const span = getDropdownLabelSpan(buttonEl); if (span) span.textContent = label != null ? String(label) : ""; if (menuEl) { menuEl.querySelectorAll(".redesigned-dropdown-item").forEach(item => { const isSelected = item.dataset.value === String(value ?? ""); item.querySelector("i")?.classList.toggle("unactive", !isSelected); }); } } function getRedesignedDropdownValue(buttonEl) { const v = buttonEl?.dataset?.value; return v == null ? "" : String(v); } function getCustomNewsSelectedType() { return getRedesignedDropdownValue(customNewsTypeButton); } function populateCustomNewsTypeDropdown({ buttonEl, menuEl, items, selectedValue = "", onSelect } = {}) { if (!buttonEl || !menuEl) return; menuEl.replaceChildren(); const sections = [ { key: "normal", label: "Normal articles" }, { key: "turning_point", label: "Turning points" }, { key: "custom", label: "Custom article" } ]; const mkSectionTitle = (label) => { const el = document.createElement("li"); el.className = "custom-dropdown-section-title bold-font"; el.textContent = label; return el; }; const mkItem = (item) => { const a = document.createElement("a"); a.className = "redesigned-dropdown-item"; a.href = "#"; a.dataset.value = String(item.value ?? ""); const text = document.createElement("span"); text.textContent = String(item.label ?? ""); const icon = document.createElement("i"); icon.className = "bi bi-check unactive"; a.append(text, icon); a.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); setRedesignedDropdownSelection(buttonEl, menuEl, a.dataset.value, text.textContent); setRedesignedDropdownOpen(buttonEl, false); if (typeof onSelect === "function") onSelect(a.dataset.value); }); return a; }; sections.forEach(section => { const sectionItems = (items || []).filter(item => (item.group || "normal") === section.key); if (!sectionItems.length) return; menuEl.appendChild(mkSectionTitle(section.label)); sectionItems.forEach(item => { menuEl.appendChild(mkItem(item)); }); }); const selectedItem = (items || []).find(item => String(item.value ?? "") === String(selectedValue ?? "")); if (selectedItem) { setRedesignedDropdownSelection(buttonEl, menuEl, selectedItem.value, selectedItem.label); } else { setRedesignedDropdownSelection(buttonEl, menuEl, "", "Select type"); } } function populateRedesignedDropdown({ buttonEl, menuEl, items, getValue, getLabel, placeholderLabel = "Select...", includeEmpty = false, emptyLabel = "Select...", selectedValue = "", bindToggle = true, onSelect } = {}) { if (!buttonEl || !menuEl) return; if (bindToggle) bindRedesignedDropdownToggle(buttonEl); const isDriverDropdownItem = (item) => { if (!item || typeof item !== "object") return false; const hasDriverId = item.driverId != null || item.id != null; return hasDriverId && typeof item.name === "string"; }; const buildDriverDropdownContent = (item, label) => { const wrap = document.createElement("div"); wrap.className = "custom-news-driver-option"; const logoTeamId = Number(item?.displayTeamId ?? item?.teamId); const logoSrc = logoTeamId > 0 ? logos_disc?.[logoTeamId] : null; if (logoSrc) { const logo = document.createElement("img"); logo.className = "custom-news-driver-option-logo"; logo.src = logoSrc; logo.alt = item?.displayTeamName || item?.teamName || `Team ${logoTeamId}`; wrap.appendChild(logo); } const text = document.createElement("span"); text.className = "custom-news-driver-option-text"; text.textContent = String(label ?? item?.name ?? ""); wrap.appendChild(text); return wrap; }; const mkItem = (value, label) => { const a = document.createElement("a"); a.className = "redesigned-dropdown-item"; a.href = "#"; a.dataset.value = value == null ? "" : String(value); let text = document.createElement("span"); text.textContent = String(label ?? ""); const sourceItem = (items || []).find(it => String(getValue(it)) === a.dataset.value); if (isDriverDropdownItem(sourceItem)) { text = buildDriverDropdownContent(sourceItem, label); } const icon = document.createElement("i"); icon.className = "bi bi-check unactive"; a.appendChild(text); a.appendChild(icon); a.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); setRedesignedDropdownSelection(buttonEl, menuEl, a.dataset.value, text.textContent); setRedesignedDropdownOpen(buttonEl, false); if (typeof onSelect === "function") onSelect(a.dataset.value); }); return a; }; menuEl.replaceChildren(); if (includeEmpty) { menuEl.appendChild(mkItem("", emptyLabel)); } (items || []).forEach(it => { menuEl.appendChild(mkItem(getValue(it), getLabel(it))); }); const selectedStr = selectedValue == null ? "" : String(selectedValue); const selectedItem = Array.from(menuEl.querySelectorAll(".redesigned-dropdown-item")) .find(a => a.dataset.value === selectedStr); if (selectedItem) { setRedesignedDropdownSelection(buttonEl, menuEl, selectedItem.dataset.value, selectedItem.querySelector("span")?.textContent); } else { setRedesignedDropdownSelection(buttonEl, menuEl, "", placeholderLabel); } } function resolveCustomNewsTitleType(type = getCustomNewsSelectedType()) { if (type === "team_comparison") { return getRedesignedDropdownValue(document.getElementById("customNewsCompTypeButton")) === "bad" ? 11 : 12; } if (type === "season_review") { return Number(getRedesignedDropdownValue(document.getElementById("customNewsPartButton"))) === 3 ? 15 : 14; } return CUSTOM_NEWS_TYPE_META[type]?.titleType ?? null; } function getCustomNewsTitleTemplates(type = getCustomNewsSelectedType()) { const meta = CUSTOM_NEWS_TYPE_META[type]; const titleType = resolveCustomNewsTitleType(type); if (!meta || titleType == null) return []; if (meta.turningPoint) { const entry = turningPointTitleTemplates.find(t => t.new_type === titleType); return Array.isArray(entry?.turning_titles) ? entry.turning_titles : []; } const entry = newsTitleTemplates.find(t => t.new_type === titleType); return Array.isArray(entry?.titles) ? entry.titles : []; } function refreshCustomNewsTitleTemplates() { if (!customNewsTemplateButton || !customNewsTemplateMenu) return; const type = getCustomNewsSelectedType(); const templates = getCustomNewsTitleTemplates(type); if (type === "custom_new") { customNewsTemplateWrap?.classList.add("custom-news-hidden"); customNewsTitleInput?.classList.remove("custom-news-hidden"); if (customNewsTitleInput) { customNewsTitleInput.placeholder = getCustomNewsTitlePlaceholder(type); } return; } customNewsTemplateWrap?.classList.remove("custom-news-hidden"); customNewsTitleInput?.classList.add("custom-news-hidden"); if (customNewsTitleInput) customNewsTitleInput.value = ""; customNewsTemplateButton.disabled = false; populateRedesignedDropdown({ buttonEl: customNewsTemplateButton, menuEl: customNewsTemplateMenu, items: templates.map((template, index) => ({ value: String(index), label: template })), getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Automatic / default title", includeEmpty: true, emptyLabel: "Automatic / default title", selectedValue: getRedesignedDropdownValue(customNewsTemplateButton), bindToggle: false }); } function updateCustomNewsImagePreview() { const imageFile = document.getElementById("customNewsImageValue")?.value || ""; document.querySelectorAll(".custom-news-image-card").forEach(card => { card.classList.toggle("selected", card.dataset.value === imageFile); }); } function bindCustomNewsLiveRefresh() { if (!customNewsModalEl || customNewsModalEl.dataset.customNewsRefreshBound === "1") return; customNewsModalEl.dataset.customNewsRefreshBound = "1"; const refreshAll = () => { refreshCustomNewsTitleTemplates(); updateCustomNewsImagePreview(); }; customNewsModalEl.addEventListener("input", refreshAll); customNewsModalEl.addEventListener("change", refreshAll); customNewsModalEl.addEventListener("click", (e) => { if (e.target.closest(".redesigned-dropdown-item")) { setTimeout(refreshAll, 0); } }); } async function loadCustomNewsOptions() { if (customNewsOptionsCache) return customNewsOptionsCache; const resp = await new Command("getCustomNewsOptions", {}).promiseExecute(); customNewsOptionsCache = resp.content || null; return customNewsOptionsCache; } function renderCustomNewsParams(type, options) { if (!customNewsParams) return; const teams = options?.teams || []; const races = options?.races || []; const rawOfficialDrivers = options?.drivers || []; const rawAllDrivers = options?.allDrivers?.length ? options.allDrivers : rawOfficialDrivers; const engines = options?.engines || []; const isF1TeamId = (teamId) => (teamId >= 1 && teamId <= 10) || teamId === 32; const isJuniorTeamId = (teamId) => teamId >= 11 && teamId <= 31; const normalizeDriverOption = (driver) => { const driverId = Number(driver?.id ?? driver?.driverId); const teamId = Number(driver?.teamId); const resolvedTeamId = teamId > 0 ? teamId : null; const resolvedTeamName = resolvedTeamId ? (combined_dict[resolvedTeamId] || driver?.teamName || `Team ${resolvedTeamId}`) : (driver?.teamName || "Free Agent"); return { ...driver, id: driverId, driverId: driver?.driverId != null ? Number(driver.driverId) : driverId, teamId: resolvedTeamId, displayTeamId: resolvedTeamId, teamName: resolvedTeamName, displayTeamName: resolvedTeamName }; }; const getDriverPriority = (driver, preferredOfficialIds = new Set()) => { const driverId = Number(driver?.id ?? driver?.driverId); const teamId = Number(driver?.teamId ?? driver?.displayTeamId); if (preferredOfficialIds.has(driverId) && isF1TeamId(teamId)) return 4; if (isF1TeamId(teamId)) return 3; if (isJuniorTeamId(teamId)) return 2; if (teamId > 0) return 1; return 0; }; const dedupeDriverOptions = (list, preferredOfficialIds = new Set()) => { const byId = new Map(); const orderedIds = []; (Array.isArray(list) ? list : []).forEach((driver) => { const normalized = normalizeDriverOption(driver); const driverId = Number(normalized?.id); if (!(driverId > 0)) return; if (!byId.has(driverId)) { byId.set(driverId, normalized); orderedIds.push(driverId); return; } const current = byId.get(driverId); const currentPriority = getDriverPriority(current, preferredOfficialIds); const nextPriority = getDriverPriority(normalized, preferredOfficialIds); if (nextPriority > currentPriority || (nextPriority === currentPriority && current?.teamId == null && normalized?.teamId != null)) { byId.set(driverId, normalized); } }); return orderedIds.map((driverId) => byId.get(driverId)).filter(Boolean); }; const dedupedOfficialDrivers = dedupeDriverOptions(rawOfficialDrivers); const officialDriverIds = new Set(dedupedOfficialDrivers.map(driver => Number(driver?.id)).filter(id => id > 0)); const allDrivers = dedupeDriverOptions(rawAllDrivers, officialDriverIds); const driversById = new Map(allDrivers.map(driver => [Number(driver?.id), driver])); const officialDrivers = dedupedOfficialDrivers.map(driver => driversById.get(Number(driver?.id)) || driver); const drivers = officialDrivers; const f1OfficialDrivers = officialDrivers.filter(driver => isF1TeamId(Number(driver?.teamId))); const nonOfficialDrivers = allDrivers.filter(driver => !officialDriverIds.has(Number(driver?.id))); const completedRaces = races.filter(race => Number(race?.state) === 2); customNewsParams.replaceChildren(); const createEl = (tag, className) => { const el = document.createElement(tag); if (className) el.className = className; return el; }; const makeLabel = (_targetId, text) => { const label = createEl("span", "custom-news-label bold-font"); label.textContent = text; return label; }; const makeRow = () => createEl("div", "custom-news-row"); const makeCol = (width = "full") => { const col = createEl("div", "custom-news-stack"); const span = width === "quarter" ? 3 : width === "third" ? 4 : width === "half" ? 6 : 12; col.dataset.span = String(span); return col; }; const makeInput = ({ id, type = "text", placeholder = "", min = null, max = null, step = null, value = null } = {}) => { const input = createEl("input", "custom-news-input"); input.id = id; input.type = type; if (placeholder) input.placeholder = placeholder; if (min != null) input.min = String(min); if (max != null) input.max = String(max); if (step != null) input.step = String(step); if (value != null) input.value = String(value); return input; }; const makeTextarea = ({ id, placeholder = "" } = {}) => { const textarea = createEl("textarea", "custom-news-input"); textarea.id = id; textarea.placeholder = placeholder; return textarea; }; const makeInfo = (text) => { const info = createEl("div", "custom-news-info"); info.textContent = text; return info; }; const makeHint = (text) => { const hint = createEl("div", "custom-news-help"); hint.textContent = text; return hint; }; const makeSwitch = ({ id, label, checked = false } = {}) => { const wrap = createEl("div", "form-check form-switch custom-news-switch"); const input = document.createElement("input"); input.className = "form-check-input custom-toggle"; input.type = "checkbox"; input.role = "switch"; input.id = id; input.checked = !!checked; const labelEl = document.createElement("label"); labelEl.className = "form-check-label"; labelEl.htmlFor = id; labelEl.textContent = label; wrap.append(input, labelEl); return { wrap, input, labelEl }; }; const makeCheckbox = ({ id, label, checked = false } = {}) => { const wrap = createEl("label", "custom-news-checkbox"); wrap.htmlFor = id; const input = document.createElement("input"); input.className = "form-check-input"; input.type = "checkbox"; input.id = id; input.checked = !!checked; const text = createEl("span", "custom-news-checkbox-label"); text.textContent = label; wrap.append(input, text); return { wrap, input, text }; }; const clampNumber = (value, min = null, max = null) => { let nextValue = Number(value); if (Number.isNaN(nextValue)) nextValue = 0; if (min != null) nextValue = Math.max(Number(min), nextValue); if (max != null) nextValue = Math.min(Number(max), nextValue); return nextValue; }; const formatNumberValue = (value, step = 1) => { const decimals = (String(step).split(".")[1] || "").length; const numericValue = Number(value) || 0; return decimals > 0 ? numericValue.toFixed(decimals) : String(Math.round(numericValue)); }; const makeNumberControl = ({ id, value = 0, min = null, max = null, step = 1, placeholder = "" } = {}) => { const wrap = createEl("div", "custom-news-number-control"); const minus = document.createElement("button"); minus.type = "button"; minus.className = "bi bi-dash new-augment-button custom-news-step-button"; const input = makeInput({ id, type: "number", placeholder, min, max, step }); input.classList.add("custom-news-number-input"); input.value = formatNumberValue(clampNumber(value, min, max), step); const plus = document.createElement("button"); plus.type = "button"; plus.className = "bi bi-plus new-augment-button custom-news-step-button"; const applyDelta = (delta) => { const currentValue = input.value === "" ? 0 : Number(input.value); const nextValue = clampNumber((Number.isNaN(currentValue) ? 0 : currentValue) + delta, min, max); input.value = formatNumberValue(nextValue, step); input.dispatchEvent(new Event("change", { bubbles: true })); }; minus.addEventListener("click", () => { if (input.disabled) return; applyDelta(-step); }); plus.addEventListener("click", () => { if (input.disabled) return; applyDelta(step); }); input.addEventListener("change", () => { if (input.value === "") return; input.value = formatNumberValue(clampNumber(input.value, min, max), step); }); const setDisabled = (disabled) => { input.disabled = !!disabled; wrap.classList.toggle("disabled", !!disabled); minus.disabled = !!disabled; plus.disabled = !!disabled; }; wrap.append(minus, input, plus); return { wrap, input, setDisabled }; }; const makeDropdown = ({ buttonId, menuId, placeholder }) => { const wrap = createEl("div", "dropdown-global"); const button = createEl("button", "redesigned-dropdown bold-font"); button.type = "button"; button.id = buttonId; button.setAttribute("aria-haspopup", "true"); button.setAttribute("aria-expanded", "false"); button.dataset.value = ""; const labelSpan = createEl("span", "dropdown-label"); labelSpan.textContent = placeholder; const chevron = createEl("i", "redesigned-chevron"); button.append(labelSpan, chevron); const menu = createEl("ul", "redesigned-dropdown-menu"); menu.id = menuId; menu.style.fontFamily = "Formula1"; wrap.append(button, menu); return { wrap, button, menu }; }; const teamLabel = (t) => t?.name ?? ""; const raceLabel = (r) => `${r?.label ?? ""}${r?.state === 2 ? " (Done)" : ""}`; const driverLabel = (d) => `${d?.name ?? ""}`; const getTeamDrivers = (teamId) => officialDrivers.filter(driver => Number(driver?.teamId) === Number(teamId)); const getDriversOutsideTeam = (teamId) => allDrivers.filter(driver => Number(driver?.teamId) !== Number(teamId)); const getSwitchModeLabel = (checked) => checked ? "Manual values" : "Random values"; const setDropdownDisabled = (buttonEl, disabled) => { if (!buttonEl) return; buttonEl.disabled = !!disabled; buttonEl.classList.toggle("disabled", !!disabled); if (disabled) setRedesignedDropdownOpen(buttonEl, false); }; if (type === "race_result" || type === "quali_result" || type === "potential_champion" || type === "world_champion") { const row = makeRow(); const col = makeCol("full"); const { wrap } = makeDropdown({ buttonId: "customNewsRaceButton", menuId: "customNewsRaceMenu", placeholder: "Select race" }); col.append(makeLabel("customNewsRaceButton", "Race"), wrap); row.append(col); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsRaceButton"), menuEl: document.getElementById("customNewsRaceMenu"), items: completedRaces, getValue: r => r.id, getLabel: raceLabel, placeholderLabel: "Select race", includeEmpty: true, emptyLabel: "Select race", selectedValue: "", bindToggle: true }); return; } if (type === "race_reaction") { const row = makeRow(); const raceCol = makeCol("full"); raceCol.append( makeLabel("customNewsRaceButton", "Race"), makeDropdown({ buttonId: "customNewsRaceButton", menuId: "customNewsRaceMenu", placeholder: "Select race" }).wrap ); const happyCol = makeCol("half"); happyCol.append( makeLabel("customNewsHappyDriverButton", "Happy driver (optional)"), makeDropdown({ buttonId: "customNewsHappyDriverButton", menuId: "customNewsHappyDriverMenu", placeholder: "Random" }).wrap ); const unhappyCol = makeCol("half"); unhappyCol.append( makeLabel("customNewsUnhappyDriverButton", "Unhappy driver (optional)"), makeDropdown({ buttonId: "customNewsUnhappyDriverButton", menuId: "customNewsUnhappyDriverMenu", placeholder: "Random" }).wrap ); row.append(raceCol, happyCol, unhappyCol); customNewsParams.append(row); const happyBtn = document.getElementById("customNewsHappyDriverButton"); const happyMenu = document.getElementById("customNewsHappyDriverMenu"); const unhappyBtn = document.getElementById("customNewsUnhappyDriverButton"); const unhappyMenu = document.getElementById("customNewsUnhappyDriverMenu"); const raceDriverLabel = (d) => `${d?.pos}. ${d?.name}`; const resetRaceReactionDrivers = () => { populateRedesignedDropdown({ buttonEl: happyBtn, menuEl: happyMenu, items: [], getValue: d => d.driverId, getLabel: raceDriverLabel, placeholderLabel: "Random", includeEmpty: true, emptyLabel: "Random", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: unhappyBtn, menuEl: unhappyMenu, items: [], getValue: d => d.driverId, getLabel: raceDriverLabel, placeholderLabel: "Random", includeEmpty: true, emptyLabel: "Random", selectedValue: "", bindToggle: true }); }; resetRaceReactionDrivers(); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsRaceButton"), menuEl: document.getElementById("customNewsRaceMenu"), items: completedRaces, getValue: r => r.id, getLabel: raceLabel, placeholderLabel: "Select race", includeEmpty: true, emptyLabel: "Select race", selectedValue: "", bindToggle: true, onSelect: async (raceIdValue) => { const raceId = Number(raceIdValue); if (!raceId || raceId <= 0) { resetRaceReactionDrivers(); return; } try { const resp = await new Command("customNewsRaceDrivers", { raceId }).promiseExecute(); const list = Array.isArray(resp.content) ? resp.content : []; populateRedesignedDropdown({ buttonEl: happyBtn, menuEl: happyMenu, items: list, getValue: d => d.driverId, getLabel: raceDriverLabel, placeholderLabel: "Random", includeEmpty: true, emptyLabel: "Random", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: unhappyBtn, menuEl: unhappyMenu, items: list, getValue: d => d.driverId, getLabel: raceDriverLabel, placeholderLabel: "Random", includeEmpty: true, emptyLabel: "Random", selectedValue: "", bindToggle: true }); } catch (e) { console.error("Failed to load race drivers:", e); } } }); return; } if (type === "fake_transfer") { const row = makeRow(); const col = makeCol("full"); col.append( makeLabel("customNewsDriverButton", "Driver"), makeDropdown({ buttonId: "customNewsDriverButton", menuId: "customNewsDriverMenu", placeholder: "Select driver" }).wrap ); row.append(col); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDriverButton"), menuEl: document.getElementById("customNewsDriverMenu"), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); return; } if (type === "big_transfer" || type === "massive_exit" || type === "massive_signing") { const row = makeRow(); const driverCol = makeCol("half"); driverCol.append( makeLabel("customNewsDriverButton", "Driver"), makeDropdown({ buttonId: "customNewsDriverButton", menuId: "customNewsDriverMenu", placeholder: "Select driver" }).wrap ); const fromCol = makeCol("quarter"); fromCol.append( makeLabel("customNewsFromTeamButton", "From team"), makeDropdown({ buttonId: "customNewsFromTeamButton", menuId: "customNewsFromTeamMenu", placeholder: "Select team" }).wrap ); const toCol = makeCol("quarter"); toCol.append( makeLabel("customNewsToTeamButton", "To team"), makeDropdown({ buttonId: "customNewsToTeamButton", menuId: "customNewsToTeamMenu", placeholder: "Select team" }).wrap ); const salaryCol = makeCol("half"); salaryCol.append( makeLabel("customNewsSalary", "Salary (optional)"), makeInput({ id: "customNewsSalary", type: "number", placeholder: "e.g. 2000000", min: 0 }) ); const endSeasonCol = makeCol("half"); endSeasonCol.append( makeLabel("customNewsEndSeason", "Contract end season (optional)"), makeInput({ id: "customNewsEndSeason", type: "number", placeholder: "e.g. 2028", min: 0 }) ); row.append(driverCol, fromCol, toCol, salaryCol, endSeasonCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDriverButton"), menuEl: document.getElementById("customNewsDriverMenu"), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsFromTeamButton"), menuEl: document.getElementById("customNewsFromTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsToTeamButton"), menuEl: document.getElementById("customNewsToTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); return; } if (type === "contract_renewal") { const row = makeRow(); const driverCol = makeCol("half"); driverCol.append( makeLabel("customNewsDriverButton", "Driver"), makeDropdown({ buttonId: "customNewsDriverButton", menuId: "customNewsDriverMenu", placeholder: "Select driver" }).wrap ); const renewalCol = makeCol("quarter"); renewalCol.append( makeLabel("customNewsRenewalTeamButton", "Renewal team"), makeDropdown({ buttonId: "customNewsRenewalTeamButton", menuId: "customNewsRenewalTeamMenu", placeholder: "Select team" }).wrap ); const currentCol = makeCol("quarter"); currentCol.append( makeLabel("customNewsCurrentTeamButton", "Current team"), makeDropdown({ buttonId: "customNewsCurrentTeamButton", menuId: "customNewsCurrentTeamMenu", placeholder: "Select team" }).wrap ); const salaryCol = makeCol("half"); salaryCol.append( makeLabel("customNewsSalary", "Salary (optional)"), makeInput({ id: "customNewsSalary", type: "number", placeholder: "e.g. 2000000", min: 0 }) ); const endSeasonCol = makeCol("half"); endSeasonCol.append( makeLabel("customNewsEndSeason", "Contract end season (optional)"), makeInput({ id: "customNewsEndSeason", type: "number", placeholder: "e.g. 2028", min: 0 }) ); row.append(driverCol, renewalCol, currentCol, salaryCol, endSeasonCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDriverButton"), menuEl: document.getElementById("customNewsDriverMenu"), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsRenewalTeamButton"), menuEl: document.getElementById("customNewsRenewalTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsCurrentTeamButton"), menuEl: document.getElementById("customNewsCurrentTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); return; } if (type === "silly_season_rumors") { const row = makeRow(); [1, 2, 3].forEach(i => { const driverCol = makeCol("half"); driverCol.append( makeLabel(`customNewsSillyDriver${i}Button`, `Driver ${i}`), makeDropdown({ buttonId: `customNewsSillyDriver${i}Button`, menuId: `customNewsSillyDriver${i}Menu`, placeholder: "Select driver" }).wrap ); const teamCol = makeCol("half"); teamCol.append( makeLabel(`customNewsSillyTeam${i}Button`, `Destination team ${i}`), makeDropdown({ buttonId: `customNewsSillyTeam${i}Button`, menuId: `customNewsSillyTeam${i}Menu`, placeholder: "Select team" }).wrap ); row.append(driverCol, teamCol); }); customNewsParams.append(row); [1, 2, 3].forEach(i => { populateRedesignedDropdown({ buttonEl: document.getElementById(`customNewsSillyDriver${i}Button`), menuEl: document.getElementById(`customNewsSillyDriver${i}Menu`), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById(`customNewsSillyTeam${i}Button`), menuEl: document.getElementById(`customNewsSillyTeam${i}Menu`), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); }); return; } if (type === "team_comparison") { const row = makeRow(); const teamCol = makeCol("half"); teamCol.append( makeLabel("customNewsTeamButton", "Team"), makeDropdown({ buttonId: "customNewsTeamButton", menuId: "customNewsTeamMenu", placeholder: "Select team" }).wrap ); const compCol = makeCol("quarter"); compCol.append( makeLabel("customNewsCompTypeButton", "Comparison"), makeDropdown({ buttonId: "customNewsCompTypeButton", menuId: "customNewsCompTypeMenu", placeholder: "Select" }).wrap ); const dropCol = makeCol("quarter"); dropCol.append( makeLabel("customNewsDrop", "Points diff (optional)"), makeInput({ id: "customNewsDrop", type: "number", value: 0 }) ); row.append(teamCol, compCol, dropCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsTeamButton"), menuEl: document.getElementById("customNewsTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsCompTypeButton"), menuEl: document.getElementById("customNewsCompTypeMenu"), items: [{ value: "good", label: "Good" }, { value: "bad", label: "Bad" }], getValue: t => t.value, getLabel: t => t.label, placeholderLabel: "Select", includeEmpty: false, selectedValue: "good", bindToggle: true }); return; } if (type === "driver_comparison") { const row = makeRow(); const teamCol = makeCol("third"); teamCol.append( makeLabel("customNewsTeamButton", "Team"), makeDropdown({ buttonId: "customNewsTeamButton", menuId: "customNewsTeamMenu", placeholder: "Select team" }).wrap ); const driver1Col = makeCol("third"); driver1Col.append( makeLabel("customNewsDriver1Button", "Driver 1"), makeDropdown({ buttonId: "customNewsDriver1Button", menuId: "customNewsDriver1Menu", placeholder: "Select driver" }).wrap ); const driver2Col = makeCol("third"); driver2Col.append( makeLabel("customNewsDriver2Button", "Driver 2"), makeDropdown({ buttonId: "customNewsDriver2Button", menuId: "customNewsDriver2Menu", placeholder: "Select driver" }).wrap ); row.append(teamCol, driver1Col, driver2Col); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsTeamButton"), menuEl: document.getElementById("customNewsTeamMenu"), items: teams, getValue: t => t.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDriver1Button"), menuEl: document.getElementById("customNewsDriver1Menu"), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDriver2Button"), menuEl: document.getElementById("customNewsDriver2Menu"), items: drivers, getValue: d => d.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true }); return; } if (type === "season_review") { const row = makeRow(); const col = makeCol("half"); col.append( makeLabel("customNewsPartButton", "Season review part"), makeDropdown({ buttonId: "customNewsPartButton", menuId: "customNewsPartMenu", placeholder: "Select part" }).wrap ); row.append(col); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsPartButton"), menuEl: document.getElementById("customNewsPartMenu"), items: [{ value: "1", label: "Part 1" }, { value: "2", label: "Part 2" }, { value: "3", label: "Part 3" }], getValue: t => t.value, getLabel: t => t.label, placeholderLabel: "Select part", includeEmpty: false, selectedValue: "1", bindToggle: true }); return; } if (type === "turning_point_technical_directive") { const row = makeRow(); const componentCol = makeCol("half"); componentCol.append( makeLabel("customNewsComponentButton", "Component"), makeDropdown({ buttonId: "customNewsComponentButton", menuId: "customNewsComponentMenu", placeholder: "Select component" }).wrap ); const reasonCol = makeCol("half"); reasonCol.append( makeLabel("customNewsReason", "Reason"), makeInput({ id: "customNewsReason", placeholder: "e.g. improve safety" }) ); const modeCol = makeCol("half"); modeCol.append( makeLabel("customNewsEffectModeButton", "Effect style"), makeDropdown({ buttonId: "customNewsEffectModeButton", menuId: "customNewsEffectModeMenu", placeholder: "Select style" }).wrap ); const amountCol = makeCol("quarter"); const tdAmountControl = makeNumberControl({ id: "customNewsEffectAmount", value: 5, min: 0.5, max: 15, step: 0.5 }); amountCol.append( makeLabel("customNewsEffectAmount", "Max swing(%)"), tdAmountControl.wrap ); row.append(componentCol, reasonCol, modeCol, amountCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsComponentButton"), menuEl: document.getElementById("customNewsComponentMenu"), items: Object.entries(part_full_names) .filter(([id]) => ["3", "4", "5", "6", "7", "8"].includes(String(id))) .map(([id, label]) => ({ id, label })), getValue: item => item.id, getLabel: item => item.label, placeholderLabel: "Select component", includeEmpty: true, emptyLabel: "Select component", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsEffectModeButton"), menuEl: document.getElementById("customNewsEffectModeMenu"), items: [ { value: "spread", label: "Spread grid" }, { value: "compact", label: "Compact grid" }, { value: "random", label: "Random" } ], getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select style", includeEmpty: false, selectedValue: "spread", bindToggle: true }); return; } if (type === "turning_point_transfer") { const row = makeRow(); const teamCol = makeCol("third"); teamCol.append( makeLabel("customNewsTeamButton", "Team"), makeDropdown({ buttonId: "customNewsTeamButton", menuId: "customNewsTeamMenu", placeholder: "Select team" }).wrap ); const driverOutCol = makeCol("third"); driverOutCol.append( makeLabel("customNewsDriverOutButton", "Driver out"), makeDropdown({ buttonId: "customNewsDriverOutButton", menuId: "customNewsDriverOutMenu", placeholder: "Select driver" }).wrap ); const driverInCol = makeCol("third"); driverInCol.append( makeLabel("customNewsDriverInButton", "Driver in"), makeDropdown({ buttonId: "customNewsDriverInButton", menuId: "customNewsDriverInMenu", placeholder: "Select driver" }).wrap ); const reserveCol = makeCol("half"); reserveCol.append( makeLabel("customNewsReserveDriverButton", "Substitute driver for driver in"), makeDropdown({ buttonId: "customNewsReserveDriverButton", menuId: "customNewsReserveDriverMenu", placeholder: "No substitute" }).wrap ); row.append(teamCol, driverOutCol, driverInCol, reserveCol); customNewsParams.append(row); const teamButton = document.getElementById("customNewsTeamButton"); const driverOutButton = document.getElementById("customNewsDriverOutButton"); const driverOutMenu = document.getElementById("customNewsDriverOutMenu"); const driverInButton = document.getElementById("customNewsDriverInButton"); const driverInMenu = document.getElementById("customNewsDriverInMenu"); const reserveButton = document.getElementById("customNewsReserveDriverButton"); const reserveMenu = document.getElementById("customNewsReserveDriverMenu"); const syncTransferReserveDropdown = (driverInIdValue) => { const driverInId = Number(driverInIdValue); const isOfficialSeatDriver = officialDriverIds.has(driverInId); const reserveLabel = isOfficialSeatDriver ? "No substitute" : "Only available for drivers with an F1 seat"; populateRedesignedDropdown({ buttonEl: reserveButton, menuEl: reserveMenu, items: isOfficialSeatDriver ? nonOfficialDrivers : [], getValue: item => item.id, getLabel: driverLabel, placeholderLabel: reserveLabel, includeEmpty: true, emptyLabel: reserveLabel, selectedValue: isOfficialSeatDriver ? getRedesignedDropdownValue(reserveButton) : "", bindToggle: true }); setDropdownDisabled(reserveButton, !isOfficialSeatDriver); }; const syncTransferDriverDropdowns = (teamIdValue) => { const selectedTeamId = Number(teamIdValue); const selectedTeamDrivers = selectedTeamId > 0 ? getTeamDrivers(selectedTeamId) : []; const externalDrivers = selectedTeamId > 0 ? getDriversOutsideTeam(selectedTeamId) : []; const teamFirstLabel = selectedTeamId > 0 ? "Select driver" : "Select team first"; populateRedesignedDropdown({ buttonEl: driverOutButton, menuEl: driverOutMenu, items: selectedTeamDrivers, getValue: item => item.id, getLabel: driverLabel, placeholderLabel: teamFirstLabel, includeEmpty: true, emptyLabel: teamFirstLabel, selectedValue: getRedesignedDropdownValue(driverOutButton), bindToggle: true }); populateRedesignedDropdown({ buttonEl: driverInButton, menuEl: driverInMenu, items: externalDrivers, getValue: item => item.id, getLabel: driverLabel, placeholderLabel: teamFirstLabel, includeEmpty: true, emptyLabel: teamFirstLabel, selectedValue: getRedesignedDropdownValue(driverInButton), bindToggle: true, onSelect: syncTransferReserveDropdown }); if (selectedTeamId > 0) { syncTransferReserveDropdown(getRedesignedDropdownValue(driverInButton)); } else { syncTransferReserveDropdown(""); } }; populateRedesignedDropdown({ buttonEl: teamButton, menuEl: document.getElementById("customNewsTeamMenu"), items: teams, getValue: item => item.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true, onSelect: syncTransferDriverDropdowns }); syncTransferDriverDropdowns(""); return; } if (type === "turning_point_investment") { const row = makeRow(); const teamCol = makeCol("half"); teamCol.append( makeLabel("customNewsTeamButton", "Team"), makeDropdown({ buttonId: "customNewsTeamButton", menuId: "customNewsTeamMenu", placeholder: "Select team" }).wrap ); const countryCol = makeCol("half"); countryCol.append( makeLabel("customNewsCountryButton", "Investor country"), makeDropdown({ buttonId: "customNewsCountryButton", menuId: "customNewsCountryMenu", placeholder: "Select country" }).wrap ); const amountCol = makeCol("half"); amountCol.append( makeLabel("customNewsInvestmentAmount", "Investment amount (millions)"), makeInput({ id: "customNewsInvestmentAmount", type: "number", min: 1, value: 60 }) ); const shareCol = makeCol("half"); shareCol.append( makeLabel("customNewsInvestmentShare", "Share purchased (%)"), makeInput({ id: "customNewsInvestmentShare", type: "number", min: 1, value: 20 }) ); row.append(teamCol, countryCol, amountCol, shareCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsTeamButton"), menuEl: document.getElementById("customNewsTeamMenu"), items: teams, getValue: item => item.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsCountryButton"), menuEl: document.getElementById("customNewsCountryMenu"), items: CUSTOM_NEWS_INVESTMENT_COUNTRIES.map(country => ({ value: country, label: country })), getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select country", includeEmpty: true, emptyLabel: "Select country", selectedValue: "", bindToggle: true }); return; } if (type === "turning_point_dsq") { const row = makeRow(); const raceCol = makeCol("half"); raceCol.append( makeLabel("customNewsRaceButton", "Race"), makeDropdown({ buttonId: "customNewsRaceButton", menuId: "customNewsRaceMenu", placeholder: "Select race" }).wrap ); const teamCol = makeCol("half"); teamCol.append( makeLabel("customNewsTeamButton", "Team"), makeDropdown({ buttonId: "customNewsTeamButton", menuId: "customNewsTeamMenu", placeholder: "Select team" }).wrap ); const componentCol = makeCol("full"); componentCol.append( makeLabel("customNewsDsqComponentButton", "Component"), makeDropdown({ buttonId: "customNewsDsqComponentButton", menuId: "customNewsDsqComponentMenu", placeholder: "Select component" }).wrap ); row.append(raceCol, teamCol, componentCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsRaceButton"), menuEl: document.getElementById("customNewsRaceMenu"), items: completedRaces, getValue: item => item.id, getLabel: raceLabel, placeholderLabel: "Select race", includeEmpty: true, emptyLabel: "Select race", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsTeamButton"), menuEl: document.getElementById("customNewsTeamMenu"), items: teams, getValue: item => item.id, getLabel: teamLabel, placeholderLabel: "Select team", includeEmpty: true, emptyLabel: "Select team", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsDsqComponentButton"), menuEl: document.getElementById("customNewsDsqComponentMenu"), items: CUSTOM_NEWS_DSQ_COMPONENTS.map(component => ({ value: component, label: component })), getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select component", includeEmpty: true, emptyLabel: "Select component", selectedValue: "", bindToggle: true }); return; } if (type === "turning_point_race_substitution") { const row = makeRow(); const raceCol = makeCol("half"); raceCol.append( makeLabel("customNewsRaceButton", "Original race"), makeDropdown({ buttonId: "customNewsRaceButton", menuId: "customNewsRaceMenu", placeholder: "Select race" }).wrap ); const replacementCol = makeCol("half"); replacementCol.append( makeLabel("customNewsReplacementTrackButton", "Replacement track"), makeDropdown({ buttonId: "customNewsReplacementTrackButton", menuId: "customNewsReplacementTrackMenu", placeholder: "Select track" }).wrap ); const reasonCol = makeCol("half"); reasonCol.append( makeLabel("customNewsReason", "Reason"), makeInput({ id: "customNewsReason", placeholder: "e.g. logistical challenges" }) ); row.append(raceCol, replacementCol, reasonCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsRaceButton"), menuEl: document.getElementById("customNewsRaceMenu"), items: races, getValue: item => item.id, getLabel: raceLabel, placeholderLabel: "Select race", includeEmpty: true, emptyLabel: "Select race", selectedValue: "", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsReplacementTrackButton"), menuEl: document.getElementById("customNewsReplacementTrackMenu"), items: races, getValue: item => item.trackId, getLabel: item => countries_data?.[item?.code]?.country || item?.label || "", placeholderLabel: "Select track", includeEmpty: true, emptyLabel: "Select track", selectedValue: "", bindToggle: true }); return; } if (type === "turning_point_injury") { const row = makeRow(); const driverCol = makeCol("half"); driverCol.append( makeLabel("customNewsDriverButton", "Affected driver"), makeDropdown({ buttonId: "customNewsDriverButton", menuId: "customNewsDriverMenu", placeholder: "Select driver" }).wrap ); const reserveCol = makeCol("half"); reserveCol.append( makeLabel("customNewsReserveDriverButton", "Reserve driver (optional)"), makeDropdown({ buttonId: "customNewsReserveDriverButton", menuId: "customNewsReserveDriverMenu", placeholder: "No reserve driver" }).wrap ); const conditionCol = makeCol("half"); conditionCol.append( makeLabel("customNewsInjuryCondition", "Condition"), makeInput({ id: "customNewsInjuryCondition", placeholder: "e.g. a sprained wrist" }) ); const reasonCol = makeCol("half"); reasonCol.append( makeLabel("customNewsReason", "Reason"), makeInput({ id: "customNewsReason", placeholder: "e.g. a training accident" }) ); const racesAffectedCol = makeCol("half"); racesAffectedCol.append( makeLabel("customNewsRacesAffected", "Races affected"), makeInput({ id: "customNewsRacesAffected", type: "number", min: 1, value: 1 }) ); row.append(driverCol, reserveCol, conditionCol, reasonCol, racesAffectedCol); customNewsParams.append(row); const affectedDriverButton = document.getElementById("customNewsDriverButton"); const reserveDriverButton = document.getElementById("customNewsReserveDriverButton"); const reserveDriverMenu = document.getElementById("customNewsReserveDriverMenu"); const syncInjuryReserveDropdown = (driverIdValue) => { const blockedIds = new Set([Number(driverIdValue)].filter(id => id > 0)); const filteredDrivers = nonOfficialDrivers.filter(driver => !blockedIds.has(Number(driver?.id))); populateRedesignedDropdown({ buttonEl: reserveDriverButton, menuEl: reserveDriverMenu, items: filteredDrivers, getValue: item => item.id, getLabel: driverLabel, placeholderLabel: "No reserve driver", includeEmpty: true, emptyLabel: "No reserve driver", selectedValue: getRedesignedDropdownValue(reserveDriverButton), bindToggle: true }); }; populateRedesignedDropdown({ buttonEl: affectedDriverButton, menuEl: document.getElementById("customNewsDriverMenu"), items: f1OfficialDrivers, getValue: item => item.id, getLabel: driverLabel, placeholderLabel: "Select driver", includeEmpty: true, emptyLabel: "Select driver", selectedValue: "", bindToggle: true, onSelect: syncInjuryReserveDropdown }); syncInjuryReserveDropdown(""); return; } if (type === "turning_point_engine_regulation") { const row = makeRow(); const changeTypeCol = makeCol("half"); changeTypeCol.append( makeLabel("customNewsChangeTypeButton", "Change type"), makeDropdown({ buttonId: "customNewsChangeTypeButton", menuId: "customNewsChangeTypeMenu", placeholder: "Select type" }).wrap ); const changeAreaCol = makeCol("half"); changeAreaCol.append( makeLabel("customNewsChangeAreaButton", "Main change area"), makeDropdown({ buttonId: "customNewsChangeAreaButton", menuId: "customNewsChangeAreaMenu", placeholder: "Select area" }).wrap ); const modeCol = makeCol("full"); const engineModeSwitch = makeSwitch({ id: "customNewsEngineRegulationManualSwitch", label: getSwitchModeLabel(false), checked: false }); modeCol.append(makeLabel("customNewsEngineRegulationManualSwitch", "Change mode"), engineModeSwitch.wrap); const infoCol = makeCol("full"); infoCol.append(makeInfo("Set a rough percentage change per manufacturer or leave it on random.")); row.append(changeTypeCol, changeAreaCol, modeCol, infoCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsChangeTypeButton"), menuEl: document.getElementById("customNewsChangeTypeMenu"), items: [{ value: "minor", label: "Minor" }, { value: "major", label: "Major" }], getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select type", includeEmpty: false, selectedValue: "minor", bindToggle: true }); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsChangeAreaButton"), menuEl: document.getElementById("customNewsChangeAreaMenu"), items: CUSTOM_NEWS_ENGINE_CHANGE_AREAS.map(area => ({ value: area, label: area })), getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select area", includeEmpty: true, emptyLabel: "Select area", selectedValue: "", bindToggle: true }); if (engines.length) { const enginesRow = makeRow(); const engineControls = []; engines.forEach((engine) => { const engineCol = makeCol("quarter"); engineCol.classList.add("custom-news-engine-change"); engineCol.dataset.engineId = String(engine.id); const deltaInputId = `customNewsEngineDelta${engine.id}`; const deltaControl = makeNumberControl({ id: deltaInputId, value: 0, min: -20, max: 20, step: 0.5 }); engineCol.append( makeLabel(deltaInputId, `${engine.name}`), makeHint("Rough change (%)"), deltaControl.wrap ); enginesRow.append(engineCol); engineControls.push(deltaControl); }); customNewsParams.append(enginesRow); const syncEngineControls = () => { const isManual = engineModeSwitch.input.checked; engineModeSwitch.labelEl.textContent = getSwitchModeLabel(isManual); engineControls.forEach(control => control.setDisabled(!isManual)); }; engineModeSwitch.input.addEventListener("change", syncEngineControls); syncEngineControls(); } return; } if (type === "turning_point_young_drivers") { const row = makeRow(); const infoCol = makeCol("full"); infoCol.append(makeInfo("Pick two or three prospects. Approving this turning point will boost the selected drivers.")); row.append(infoCol); [1, 2, 3].forEach(i => { const col = makeCol("third"); col.append( makeLabel(`customNewsProspect${i}Button`, `Prospect ${i}${i === 3 ? " (optional)" : ""}`), makeDropdown({ buttonId: `customNewsProspect${i}Button`, menuId: `customNewsProspect${i}Menu`, placeholder: i === 3 ? "Optional" : "Select driver" }).wrap ); row.append(col); populateRedesignedDropdown({ buttonEl: document.getElementById(`customNewsProspect${i}Button`), menuEl: document.getElementById(`customNewsProspect${i}Menu`), items: officialDrivers, getValue: item => item.id, getLabel: driverLabel, placeholderLabel: i === 3 ? "Optional" : "Select driver", includeEmpty: true, emptyLabel: i === 3 ? "Optional" : "Select driver", selectedValue: "", bindToggle: true }); }); customNewsParams.append(row); return; } if (type === "turning_point_aduo") { const row = makeRow(); const quarterCol = makeCol("half"); quarterCol.append( makeLabel("customNewsAduoQuarterButton", "ADUO quarter"), makeDropdown({ buttonId: "customNewsAduoQuarterButton", menuId: "customNewsAduoQuarterMenu", placeholder: "Select quarter" }).wrap ); const modeCol = makeCol("half"); const aduoModeSwitch = makeSwitch({ id: "customNewsAduoManualSwitch", label: getSwitchModeLabel(false), checked: false }); modeCol.append(makeLabel("customNewsAduoManualSwitch", "Change mode"), aduoModeSwitch.wrap); row.append(quarterCol, modeCol); customNewsParams.append(row); populateRedesignedDropdown({ buttonEl: document.getElementById("customNewsAduoQuarterButton"), menuEl: document.getElementById("customNewsAduoQuarterMenu"), items: CUSTOM_NEWS_ADUO_QUARTERS, getValue: item => item.value, getLabel: item => item.label, placeholderLabel: "Select quarter", includeEmpty: false, selectedValue: "1", bindToggle: true }); if (engines.length) { const enginesRow = makeRow(); const aduoControls = []; engines.forEach((engine) => { const engineCol = makeCol("quarter"); engineCol.classList.add("custom-news-aduo-engine-change"); engineCol.dataset.engineId = String(engine.id); const engineCheck = makeCheckbox({ id: `customNewsAduoEnabled${engine.id}`, label: `${engine.name}`, checked: true }); const deltaInputId = `customNewsAduoDelta${engine.id}`; const deltaControl = makeNumberControl({ id: deltaInputId, value: 3, min: -12, max: 12, step: 0.5 }); engineCol.append( engineCheck.wrap, makeHint("Rough change (%)"), deltaControl.wrap ); enginesRow.append(engineCol); aduoControls.push({ control: deltaControl, checkbox: engineCheck.input }); }); customNewsParams.append(enginesRow); const syncAduoControls = () => { const isManual = aduoModeSwitch.input.checked; aduoModeSwitch.labelEl.textContent = getSwitchModeLabel(isManual); aduoControls.forEach(({ control, checkbox }) => control.setDisabled(!isManual || !checkbox.checked)); }; aduoControls.forEach(({ checkbox }) => { checkbox.addEventListener("change", syncAduoControls); }); aduoModeSwitch.input.addEventListener("change", syncAduoControls); syncAduoControls(); } return; } if (type === "custom_new") { const row = makeRow(); const galleryCol = makeCol("full"); galleryCol.append(makeLabel("customNewsImageGrid", "Picture")); const imageValue = document.createElement("input"); imageValue.type = "hidden"; imageValue.id = "customNewsImageValue"; galleryCol.append(imageValue); const imageGrid = createEl("div", "custom-news-image-grid"); imageGrid.id = "customNewsImageGrid"; CUSTOM_NEWS_IMAGE_FILES.forEach(file => { const imageCard = document.createElement("button"); imageCard.type = "button"; imageCard.className = "custom-news-image-card"; imageCard.dataset.value = file; const thumbnail = document.createElement("img"); thumbnail.src = getCustomNewsImageSrc(file); thumbnail.alt = file; thumbnail.loading = "lazy"; imageCard.append(thumbnail); imageCard.addEventListener("click", () => { imageValue.value = file; updateCustomNewsImagePreview(); }); imageGrid.appendChild(imageCard); }); galleryCol.append(imageGrid); const promptCol = makeCol("full"); const infoCol = makeCol("full"); promptCol.append( makeLabel("customNewsPrompt", "Article prompt"), makeTextarea({ id: "customNewsPrompt", placeholder: "Describe the story you want the AI to write. The article will still be grounded in the save context and current standings." }) ); row.append(galleryCol, promptCol, infoCol); customNewsParams.append(row); updateCustomNewsImagePreview(); return; } const noParams = createEl("div", "custom-news-info"); noParams.textContent = "No extra parameters for this type."; customNewsParams.append(noParams); } async function openCustomNewsModal() { if (!customNewsModalEl || !customNewsTypeButton || !customNewsTypeMenu || !customNewsCreateBtn) return; if (!customNewsModal) customNewsModal = new bootstrap.Modal(customNewsModalEl); setCustomNewsError(null); const options = await loadCustomNewsOptions(); if (!options) { setCustomNewsError("Failed to load custom news options."); return; } const defaultType = CUSTOM_NEWS_TYPE_DEFS[0]?.value || ""; populateCustomNewsTypeDropdown({ buttonEl: customNewsTypeButton, menuEl: customNewsTypeMenu, items: CUSTOM_NEWS_TYPE_DEFS, selectedValue: defaultType, onSelect: (value) => { setCustomNewsError(null); renderCustomNewsParams(String(value), options); refreshCustomNewsTitleTemplates(); } }); const defaultDate = options.currentDay ? excelToDate(options.currentDay) : new Date(); if (customNewsDateInput) customNewsDateInput.value = toIsoDate(defaultDate); if (customNewsTitleInput) customNewsTitleInput.value = ""; if (customNewsTemplateButton) { customNewsTemplateButton.disabled = false; customNewsTemplateMenu?.replaceChildren(); setRedesignedDropdownSelection(customNewsTemplateButton, customNewsTemplateMenu, "", getCustomNewsTitlePlaceholder()); setRedesignedDropdownOpen(customNewsTemplateButton, false); } customNewsTemplateWrap?.classList.remove("custom-news-hidden"); customNewsTitleInput?.classList.add("custom-news-hidden"); renderCustomNewsParams(getRedesignedDropdownValue(customNewsTypeButton), options); refreshCustomNewsTitleTemplates(); bindCustomNewsLiveRefresh(); customNewsModal.show(); } async function submitCustomNews() { if (!customNewsTypeButton) return; setCustomNewsError(null); const type = getCustomNewsSelectedType(); if (!type) { setCustomNewsError("Select a type first."); return; } const dateIso = customNewsDateInput?.value || null; const title = customNewsTitleInput?.value || ""; const titleTemplateRaw = getRedesignedDropdownValue(customNewsTemplateButton); const titleTemplateValue = titleTemplateRaw !== "__custom__" ? Number(titleTemplateRaw) : null; const titleTemplateIndex = Number.isInteger(titleTemplateValue) ? titleTemplateValue : null; const params = {}; const numFromButtonId = (id) => { const btn = document.getElementById(id); const raw = getRedesignedDropdownValue(btn); if (raw === "" || raw == null) return null; return Number(raw); }; const strFromButtonId = (id) => { const btn = document.getElementById(id); const raw = getRedesignedDropdownValue(btn); return raw || null; }; const strFromInputId = (id) => { const el = document.getElementById(id); return el?.value?.trim?.() || null; }; const numFromInputId = (id) => { const el = document.getElementById(id); if (!el || el.value === "") return null; return Number(el.value); }; const raceId = numFromButtonId("customNewsRaceButton"); if (raceId != null && raceId > 0) params.raceId = raceId; const driverId = numFromButtonId("customNewsDriverButton"); if (driverId != null && driverId > 0) params.driverId = driverId; const fromTeamId = numFromButtonId("customNewsFromTeamButton"); if (fromTeamId != null && fromTeamId > 0) params.fromTeamId = fromTeamId; const toTeamId = numFromButtonId("customNewsToTeamButton"); if (toTeamId != null && toTeamId > 0) params.toTeamId = toTeamId; const renewalTeamId = numFromButtonId("customNewsRenewalTeamButton"); if (renewalTeamId != null && renewalTeamId > 0) params.renewalTeamId = renewalTeamId; const currentTeamId = numFromButtonId("customNewsCurrentTeamButton"); if (currentTeamId != null && currentTeamId > 0) params.currentTeamId = currentTeamId; const teamId = numFromButtonId("customNewsTeamButton"); if (teamId != null && teamId > 0) params.teamId = teamId; const compType = strFromButtonId("customNewsCompTypeButton"); if (compType) params.compType = compType; const drop = numFromInputId('customNewsDrop'); if (drop != null) params.drop = drop; const driver1Id = numFromButtonId("customNewsDriver1Button"); if (driver1Id != null && driver1Id > 0) params.driver1Id = driver1Id; const driver2Id = numFromButtonId("customNewsDriver2Button"); if (driver2Id != null && driver2Id > 0) params.driver2Id = driver2Id; const part = numFromButtonId("customNewsPartButton"); if (part != null && part > 0) params.part = part; const salary = numFromInputId('customNewsSalary'); if (salary != null) params.salary = salary; const endSeason = numFromInputId('customNewsEndSeason'); if (endSeason != null) params.endSeason = endSeason; const happyDriverId = numFromButtonId("customNewsHappyDriverButton"); if (happyDriverId != null && happyDriverId > 0) params.happyDriverId = happyDriverId; const unhappyDriverId = numFromButtonId("customNewsUnhappyDriverButton"); if (unhappyDriverId != null && unhappyDriverId > 0) params.unhappyDriverId = unhappyDriverId; if (type === "silly_season_rumors") { const list = [1, 2, 3].map(i => { const dBtn = document.getElementById(`customNewsSillyDriver${i}Button`); const tBtn = document.getElementById(`customNewsSillyTeam${i}Button`); const sEl = document.getElementById(`customNewsSillySalary${i}`); const eEl = document.getElementById(`customNewsSillyEndSeason${i}`); return { driverId: (() => { const v = getRedesignedDropdownValue(dBtn); const n = Number(v); return n > 0 ? n : null; })(), potentialTeam: (() => { const v = getRedesignedDropdownValue(tBtn); const n = Number(v); return n > 0 ? n : null; })(), salary: sEl && sEl.value !== "" ? Number(sEl.value) : null, endSeason: eEl && eEl.value !== "" ? Number(eEl.value) : null }; }); params.drivers = list; } if (type === "turning_point_technical_directive") { params.componentId = numFromButtonId("customNewsComponentButton"); params.reason = strFromInputId("customNewsReason"); params.effectMode = strFromButtonId("customNewsEffectModeButton"); params.effectAmount = numFromInputId("customNewsEffectAmount"); } if (type === "turning_point_transfer") { params.driverOutId = numFromButtonId("customNewsDriverOutButton"); params.driverInId = numFromButtonId("customNewsDriverInButton"); params.reserveDriverId = numFromButtonId("customNewsReserveDriverButton"); } if (type === "turning_point_investment") { params.country = strFromButtonId("customNewsCountryButton"); params.investmentAmount = numFromInputId("customNewsInvestmentAmount"); params.investmentShare = numFromInputId("customNewsInvestmentShare"); } if (type === "turning_point_dsq") { params.component = strFromButtonId("customNewsDsqComponentButton"); } if (type === "turning_point_race_substitution") { params.newRaceTrackId = numFromButtonId("customNewsReplacementTrackButton"); params.reason = strFromInputId("customNewsReason"); } if (type === "turning_point_injury") { params.reserveDriverId = numFromButtonId("customNewsReserveDriverButton"); params.condition = strFromInputId("customNewsInjuryCondition"); params.reason = strFromInputId("customNewsReason"); params.racesAffected = numFromInputId("customNewsRacesAffected"); } if (type === "turning_point_engine_regulation") { params.changeType = strFromButtonId("customNewsChangeTypeButton"); params.changeArea = strFromButtonId("customNewsChangeAreaButton"); params.manualMode = !!document.getElementById("customNewsEngineRegulationManualSwitch")?.checked; params.engineChanges = Array.from(document.querySelectorAll(".custom-news-engine-change")).map((engineEl) => { const engineId = Number(engineEl.dataset.engineId); return { engineId, value: numFromInputId(`customNewsEngineDelta${engineId}`) }; }); } if (type === "turning_point_young_drivers") { params.prospectDriverIds = [1, 2, 3] .map(i => numFromButtonId(`customNewsProspect${i}Button`)) .filter(id => id > 0); } if (type === "turning_point_aduo") { params.quarter = numFromButtonId("customNewsAduoQuarterButton"); params.manualMode = !!document.getElementById("customNewsAduoManualSwitch")?.checked; params.engineChanges = Array.from(document.querySelectorAll(".custom-news-aduo-engine-change")).map((engineEl) => { const engineId = Number(engineEl.dataset.engineId); return { engineId, enabled: !!document.getElementById(`customNewsAduoEnabled${engineId}`)?.checked, value: numFromInputId(`customNewsAduoDelta${engineId}`) }; }); } if (type === "custom_new") { params.image = document.getElementById("customNewsImageValue")?.value || null; params.prompt = strFromInputId("customNewsPrompt"); if (!title.trim()) { setCustomNewsError("Custom articles need a title."); return; } if (!params.image) { setCustomNewsError("Select an image for the custom article."); return; } if (!params.prompt) { setCustomNewsError("Write the prompt for the custom article."); return; } } try { await new Command("createCustomNews", { type, title, titleTemplateIndex, dateIso, params }).promiseExecute(); customNewsModal?.hide(); customNewsOptionsCache = null; const newsGrid = document.querySelector(".news-grid"); newsGrid.innerHTML = ''; generateNews(); } catch (e) { console.error(e); setCustomNewsError(e?.message || "Failed to create custom news."); } } createCustomNewsBtn?.addEventListener("click", async () => { try { await openCustomNewsModal(); } catch (e) { console.error(e); } }); customNewsCreateBtn?.addEventListener("click", async () => { await submitCustomNews(); }); export function updateNewsYearsButton(message) { let years = message.yearsAvailable; const newsYearsMenu = document.getElementById("newsSeasonMenu"); const newsYearsButton = document.getElementById("newsSeasonButton"); newsYearsMenu.innerHTML = ''; if (!Array.isArray(years) || years.length === 0) { newsYearsButton.querySelector("span").innerText = "Season"; return; } years.forEach((year) => { const item = document.createElement("a"); item.classList.add("redesigned-dropdown-item"); item.href = "#"; item.dataset.value = year; item.innerText = year; item.addEventListener("click", function (e) { console.log("Selected news year:", year); newsYearsButton.querySelector("span").innerText = year; const command = new Command("getNewsFromSeason", { season: year }); command.execute(); }); newsYearsMenu.appendChild(item); }); //set the text in the button to the current year const lastYear = Math.max(...years); newsYearsButton.querySelector("span").innerText = lastYear; } async function addTurningPointContexts(prompt, date) { const command = new Command("getNews", {}); let resp = await command.promiseExecute(); let news = resp.content; const newsWithId = Object.entries(news).map(([id, n]) => ({ id, ...n })); const turningPointsOutcomes = newsWithId.filter(n => n.id.startsWith('turning_point_outcome_') || n.id.includes('_world_champion')); const aduoTurningPoints = newsWithId.filter(n => n.type === "turning_point_aduo"); if (turningPointsOutcomes.length > 0 || aduoTurningPoints.length > 0) { let number = 1; const lines = []; const addLine = (text) => { if (!text) return; lines.push(`${number++}. ${text}`); }; const formatList = (list) => { if (!list.length) return ""; if (list.length === 1) return list[0]; if (list.length === 2) return `${list[0]} and ${list[1]}`; return `${list.slice(0, -1).join(", ")} and ${list[list.length - 1]}`; }; const getAduoEnginesSummary = (engineImprovements) => { if (!Array.isArray(engineImprovements) || !engineImprovements.length) return null; const getAvgChange = (improvements) => { if (!improvements) return 0; let sum = 0; let count = 0; for (const statId of Object.keys(improvements)) { sum += Number(improvements[statId]) || 0; count += 1; } return count ? (sum / count) : 0; }; const getPowerChange = (improvements) => { if (!improvements) return null; if (improvements[10] !== undefined) return Number(improvements[10]) || 0; if (improvements["10"] !== undefined) return Number(improvements["10"]) || 0; return null; }; const ranked = engineImprovements.map((e) => { const improvements = e?.improvements || {}; const power = getPowerChange(improvements); const score = power === null ? getAvgChange(improvements) : power; return { name: e?.name || "Unknown manufacturer", score }; }) .sort((a, b) => b.score - a.score); if (!ranked.length) return null; const top = ranked.slice(0, 3); const best = Number(top[0].score) || 0; const labelFor = (score) => { if (best <= 0) return "changed"; const ratio = score / best; if (ratio >= 0.8) return "improved a lot"; if (ratio >= 0.4) return "improved a bit"; return "improved slightly"; }; const phrases = top.map((e, idx) => { const label = labelFor(e.score); if (idx === 0) return `${e.name} ${label}`; return `${e.name} ${label.replace("improved ", "")}`; }); return formatList(phrases); }; for (const tp of turningPointsOutcomes) { const turningDate = tp.date; if ((tp.turning_point_type === "positive" || tp.id.includes('_world_champion')) && Number(turningDate) <= Number(date)) { if (tp.id.includes("investment")) { addLine(`${tp.data.country} made an investment of ${tp.data.investmentAmount} million dollars into ${tp.data.teamName}, buying a ${tp.data.investmentShare}% of their racing division.`); } else if (tp.id.includes("technical_directive")) { addLine(`The FIA introduced a technical directive in relation to the ${tp.data.component} because of ${tp.data.reason}.`); } else if (tp.id.includes("dsq")) { addLine(`After the post-race technical inspection of the ${tp.data.country} GP, both cars from ${tp.data.team} were disqualified due to an ilegality with their ${tp.data.component}.`); } else if (tp.id.includes("substitution")) { addLine(`The race that was going to be held in ${tp.data.originalCountry} was cancelled due to ${tp.data.reason} and was substituted by a race in ${tp.data.substituteCountry}.`); } else if (tp.id.includes("transfer")) { addLine(`${tp.data.driver_out?.name} lost his seat at ${tp.data.team} and ${tp.data.driver_in?.name} has been signed to replace him.`); } else if (tp.id.includes("injury")) { addLine(`${tp.data.driver_affected?.name} suffered ${tp.data.condition?.condition} due to "${tp.data.condition?.reason}", causing him to miss ${tp.data.condition?.races_affected?.length || 1} race(s). ${tp.data.reserve_driver ? `He was replaced by ${tp.data.reserve_driver?.name}.` : ''}`); } else if (tp.id.includes("_world_champion")) { addLine(`${tp.data.driver_name} (${combined_dict[tp.data.driver_team_id]}) won the ${tp.data.season_year} world championship at the ${tp.data.adjective} GP`); } } } for (const tp of aduoTurningPoints) { if (Number(tp.date) > Number(date)) continue; const summary = getAduoEnginesSummary(tp?.data?.engineImprovements); if (!summary) continue; const quarterString = tp?.data?.quarterString ? `${tp.data.quarterString} quarter` : "this period"; addLine(`ADUO engine upgrades (${quarterString}): ${summary}.`); } if (lines.length) { prompt += `\n\nHere are some other events that happened through the season. Talk about them if relevant to the article:\n${lines.join("\n")}`; } } return prompt; } ================================================ FILE: src/js/frontend/performance.js ================================================ import { races_names, part_codes_abreviations, codes_dict, combined_dict, races_map, abreviations_dict, pars_abreviations, engine_stats_dict, theme_colors } from "./config"; import { colors_dict, get_colors_dict } from "./head2head"; import { manageSaveButton, game_version, attachHold, first_show_animation, selectedTheme, confirmModal } from "./renderer"; import { Command } from "../backend/command.js"; import Chart from 'chart.js/auto'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import annotationPlugin from 'chartjs-plugin-annotation'; import { getEngineLogoSrc } from "./seasonViewer.js"; const teamsPill = document.getElementById("teamsPill"); const enginesPill = document.getElementById("enginesPill"); const teamsDiv = document.getElementById("teamsDiv"); const enginesDiv = document.getElementById("enginesDiv"); const divsTeamsArray = [teamsDiv, enginesDiv] export let teamSelected; let engineSelected; let teamEngineSelected; let performanceGraph; export let teamsEngine = "teams" export let viewingGraph = true; export let performanceDetailsMode = "performance"; let actualMaxDesign = 0; let customEnginesCopy; let currentData; let performanceView = "graph"; let currentPartsStats = null; let currentTeamExpertise = null; let performanceDraftStats = null; let expertiseDraftStats = null; let performanceAnnotationsToggle = true; Chart.register(ChartDataLabels); Chart.register(annotationPlugin); const overviewAttributes = [ { key: "top_speed", label: "Top speed" }, { key: "acceleration", label: "Acceleration" }, { key: "low_speed", label: "Low speed" }, { key: "medium_speed", label: "Medium speed" }, { key: "high_speed", label: "High speed" }, { key: "drs", label: "DRS Effectiveness" }, { key: "dirty_air", label: "Dirty air tolerance" }, { key: "brake_cooling", label: "Brake cooling" }, { key: "engine_power", label: "Engine power" }, { key: "engine_cooling", label: "Engine cooling" } ]; function clampPercent(value) { let numericValue = Number(value); if (Number.isNaN(numericValue)) { return 0; } return Math.max(0, numericValue); } function setBarWidth(bar, value) { if (!bar) return; bar.style.width = clampPercent(value) + "%"; } function normalizeData(data) { let values = Object.values(data); let min = Math.min(...values); let max = Math.max(...values); let adjustedMin = min - 5; let adjustedMax = max + 5; let normalizedData = {}; for (let key in data) { if (data.hasOwnProperty(key)) { normalizedData[key] = ((data[key] - adjustedMin) / (adjustedMax - adjustedMin)) * 100; } } return normalizedData; } function readPartsStatsFromDom() { let data = {}; document.querySelectorAll(".part-performance").forEach(function (elem) { const partKey = elem.dataset.part; data[partKey] = {}; elem.querySelectorAll(".part-performance-stat").forEach(function (stat) { const statNum = stat.dataset.attribute; if (statNum === "-1" || statNum === "15") return; const input = stat.querySelector(".custom-input-number"); if (!input) return; const value = Number(String(input.value).split(" ")[0]); data[partKey][statNum] = Number.isFinite(value) ? value : 0; }); }); return data; } function applyPartsStatsToDom(data) { if (!data) return; for (let key in data) { const part = document.querySelector(`.part-performance[data-part='${key}']`); if (!part) continue; for (let stat in data[key]) { if (stat === "15") continue; const statInput = part.querySelector(`.part-performance-stat[data-attribute='${stat}'] .custom-input-number`); if (!statInput) continue; statInput.value = Number(data[key][stat]).toFixed(2); } } } function updateExpertiseModeUi() { const teamsShow = document.querySelector(".performance-show.teams-show"); if (!teamsShow) return; const isExpertise = performanceDetailsMode === "expertise"; teamsShow.classList.toggle("expertise-mode", isExpertise); document.querySelectorAll(".part-performance").forEach(function (part) { const arrows = part.querySelector(".part-performance-title .arrows"); if (arrows) arrows.classList.toggle("d-none", isExpertise); const chevron = part.querySelector(".part-performance-title .redesigned-chevron"); if (chevron) chevron.classList.toggle("d-none", isExpertise); if (isExpertise) { const statsContainer = part.querySelector(".part-performance-stats"); if (statsContainer) statsContainer.classList.remove("hidden"); const partButtons = part.querySelector(".part-performance-title .part-buttons"); if (partButtons) partButtons.classList.remove("d-none"); if (chevron) chevron.classList.remove("clicked"); } const list = part.querySelector(".parts-list"); if (list) list.classList.toggle("d-none", isExpertise); const subtitle = part.querySelector(".part-subtitle"); if (!subtitle) return; if (isExpertise) { if (subtitle.innerText !== "Expertise") { subtitle.dataset.performanceText = subtitle.innerText; } subtitle.innerText = "Expertise"; } else if (subtitle.dataset.performanceText) { subtitle.innerText = subtitle.dataset.performanceText; } }); } function setPerformanceDetailsMode(mode) { if (mode !== "performance" && mode !== "expertise") return; if (mode === performanceDetailsMode) return; if (performanceDetailsMode === "performance") { performanceDraftStats = readPartsStatsFromDom(); } else { expertiseDraftStats = readPartsStatsFromDom(); } performanceDetailsMode = mode; updatePerformanceExpertiseButton(); updateExpertiseModeUi(); if (mode === "performance") { applyPartsStatsToDom(performanceDraftStats || currentPartsStats); } else { applyPartsStatsToDom(expertiseDraftStats || currentTeamExpertise); } } function updatePerformanceExpertiseButton() { const button = document.getElementById("performanceExpertiseButton"); if (!button) return; const icon = button.querySelector("i"); const text = button.querySelector("span"); button.dataset.value = performanceDetailsMode; if (performanceDetailsMode === "expertise") { if (icon) icon.className = "bi bi-stars"; if (text) text.textContent = "Expertise"; } else { if (icon) icon.className = "bi bi-speedometer2"; if (text) text.textContent = "Performance"; } } export function load_performance(teams) { // let teams = normalizeData(teams); for (let key in teams) { if (teams.hasOwnProperty(key)) { let teamPerformance = document.querySelector(`#teamsDiv .team-performance[data-teamid='${key}']`); if (teamPerformance) { let performanceBarProgress = teamPerformance.querySelector('.performance-bar-progress'); let team_value = teamPerformance.querySelector('.team-title-value'); if (performanceBarProgress) { setBarWidth(performanceBarProgress, teams[key]); team_value.innerText = teams[key].toFixed(2) + ' %'; performanceBarProgress.dataset.overall = teams[key]; } } } } } export function load_cars(data) { for (let key in data) { let cars = document.querySelectorAll(`#carsDiv .car[data-teamid='${key}']`); cars.forEach(function (car, index) { let carNumber = parseInt(car.dataset.carnumber); index = index + 1; let bar = car.querySelector('.performance-bar-progress'); bar.dataset.overall = data[key][carNumber][0]; setBarWidth(bar, data[key][carNumber][0]); let name = car.querySelector('.team-title-name'); name.innerText = car.dataset.teamshow + " " + carNumber.toString() + " - #" + data[key][carNumber][1]; let missing_parts = data[key][carNumber][2]; let missing_copntainer = car.querySelector(".car-missing-parts") missing_copntainer.innerHTML = "" if (missing_parts.length > 0) { let list = document.createElement("span") let string = "" missing_parts.forEach(function (part) { let partName = part_codes_abreviations[part] string += partName + " " }) list.innerText = string missing_copntainer.appendChild(list) let icon = document.createElement("i") icon.classList.add("bi", "bi-exclamation-triangle-fill") missing_copntainer.appendChild(icon) } else { let icon = document.createElement("i") icon.classList.add("bi", "bi-check-all") missing_copntainer.appendChild(icon) } let value = car.querySelector(".car-missing-parts .value") value.innerText = data[key][carNumber][0].toFixed(2) + " %" missing_copntainer.appendChild(value) }) } } export function load_attributes(teams) { for (let key in teams) { for (let attribute in teams[key]) { let team = document.querySelector(`#teamsDiv .team-performance[data-teamid='${key}']`); let bar = team.querySelector(`.performance-bar-progress`); let attributeValue = teams[key][attribute]; bar.dataset[attribute] = attributeValue.toFixed(3); } } load_overview(); } export function load_car_attributes(teams) { for (let key in teams) { for (let car in teams[key]) { let carDiv = document.querySelector(`#carsDiv .car[data-teamid='${key}'][data-carnumber='${car}']`); for (let attribute in teams[key][car]) { let bar = carDiv.querySelector(`.performance-bar-progress`); let attributeValue = teams[key][car][attribute]; bar.dataset[attribute] = attributeValue.toFixed(3); } } } } export function order_by(criterion) { let teams = document.querySelectorAll(".team-performance"); let teamsArray = Array.from(teams); teamsArray.sort(function (a, b) { return b.querySelector(".performance-bar-progress").dataset[criterion] - a.querySelector(".performance-bar-progress").dataset[criterion]; }) teamsArray.forEach(function (team, index) { document.getElementById("teamsDiv").appendChild(team); let bar = team.querySelector(".performance-bar-progress"); setBarWidth(bar, bar.dataset[criterion]); team.querySelector(".team-title-value").innerText = parseFloat(bar.dataset[criterion]).toFixed(2) + " %"; let number = team.querySelector(".team-number") number.innerText = index + 1 }) let cars = document.querySelectorAll(".car-performance"); let carsArray = Array.from(cars); carsArray.sort(function (a, b) { return b.querySelector(".performance-bar-progress").dataset[criterion] - a.querySelector(".performance-bar-progress").dataset[criterion]; }) carsArray.forEach(function (car, index) { document.getElementById("carsDiv").appendChild(car); let bar = car.querySelector(".performance-bar-progress"); setBarWidth(bar, bar.dataset[criterion]); let number = car.querySelector(".performance-number") let value = car.querySelector(".car-missing-parts .value") value.innerText = parseFloat(bar.dataset[criterion]).toFixed(2) + " %"; number.innerText = index + 1 }) } const teamsCarsButton = document.getElementById("teamsCarsButton"); const teamsCarsIcon = teamsCarsButton.querySelector("i"); const teamsCarsText = teamsCarsButton.querySelector("span"); function updateTeamsCarsButton() { const mode = teamsCarsButton.dataset.value; teamsCarsIcon.className = mode === "cars" ? "bi bi-person-fill" : "bi bi-people-fill"; teamsCarsText.textContent = mode === "cars" ? "Cars" : "Teams"; } teamsCarsButton.addEventListener("click", function () { if (teamsCarsButton.dataset.value === "teams") { teamsCarsButton.dataset.value = "cars"; document.getElementById("teamsDiv").classList.add("d-none"); document.getElementById("carsDiv").classList.remove("d-none"); } else { teamsCarsButton.dataset.value = "teams"; document.getElementById("carsDiv").classList.add("d-none"); document.getElementById("teamsDiv").classList.remove("d-none"); } updateTeamsCarsButton(); }) updateTeamsCarsButton(); document.querySelector("#attributeMenu").querySelectorAll("a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector("#attributeButton span").innerText = elem.innerText; order_by(elem.dataset.attribute); }) }) /** * Pills that manage engines and teams screens and lists */ teamsPill.addEventListener("click", function () { teamsEngine = "teams" document.querySelector("#enginesPerformance").classList.add("d-none") document.querySelector("#teamsPerformance").classList.remove("d-none") document.querySelector("#carAttributeSelector").classList.remove("d-none") document.querySelector("#customEnginesButtonContainer").classList.add("d-none") if (performanceExpertiseButton) { performanceExpertiseButton.classList.toggle("d-none", performanceView !== "details"); } removeSelected() if (performanceView === "details") { document.querySelector(".save-button").classList.remove("d-none") first_show_animation() } else { document.querySelector(".save-button").classList.add("d-none") } }) enginesPill.addEventListener("click", function () { teamsEngine = "engines" document.querySelector("#teamsPerformance").classList.add("d-none") document.querySelector("#enginesPerformance").classList.remove("d-none") document.querySelector("#carAttributeSelector").classList.add("d-none") document.querySelector("#customEnginesButtonContainer").classList.remove("d-none") if (performanceExpertiseButton) { performanceExpertiseButton.classList.add("d-none"); } removeSelected() document.querySelector(".save-button").classList.remove("d-none") first_show_animation() }) export function gather_engines_data() { let engines = document.querySelectorAll("#enginesPerformance .engine-performance") let enginesData = {} engines.forEach(function (engine) { let engineID = engine.dataset.engineid let engineStats = {} engine.querySelectorAll(".engine-performance-stat").forEach(function (stat) { let attribute = stat.dataset.attribute let value = stat.querySelector(".custom-input-number").value.split(" ")[0] engineStats[attribute] = value }) enginesData[engineID] = engineStats }) return enginesData } export function update_max_design(data) { actualMaxDesign = parseInt(data) + 1; } /** * Manages the engine stats for all manufacturers * @param {Object} engineData engine stats for all manufacturers */ export function manage_engineStats(engineData) { let officialEngines = engineData.filter(function (elem) { return elem[0] <= 10 }) let customEngines = engineData.filter(function (elem) { return elem[0] > 10 }) officialEngines.forEach(function (elem) { let engineId = elem[0] let engineStats = elem[1]; let engine = document.querySelector(`[data-engineId="${engineId}"]`); for (let key in engineStats) { let value = engineStats[key]; let attribute = engine.querySelector(`.engine-performance-stat[data-attribute="${key}"]`); let input = attribute.querySelector(".custom-input-number"); let bar = attribute.querySelector(".engine-performance-progress"); input.value = value.toFixed(1); setBarWidth(bar, value); } }) load_custom_engines(customEngines) } /** * removes the team or engine selected anc changes the icon if necesssary */ function removeSelected() { let elemsSelected = document.querySelectorAll('.selected'); elemsSelected.forEach(item => { item.classList.remove('selected') }); } /** * eventListeners for all teams and engines */ document.querySelectorAll(".team").forEach(function (elem) { elem.addEventListener("click", function () { removeSelected() manageSaveButton(true, "performance") setPerformanceView("details") elem.classList.toggle('selected'); teamSelected = elem.dataset.teamid; performanceDraftStats = null; expertiseDraftStats = null; currentPartsStats = null; currentTeamExpertise = null; const command = new Command("performanceRequest", { teamID: teamSelected}); command.execute(); }) }) document.querySelectorAll(".car").forEach(function (elem) { elem.addEventListener("click", function () { removeSelected() manageSaveButton(true, "performance") setPerformanceView("details") elem.classList.toggle('selected'); teamSelected = elem.dataset.teamid; performanceDraftStats = null; expertiseDraftStats = null; currentPartsStats = null; currentTeamExpertise = null; const command = new Command("performanceRequest", { teamID: teamSelected}); command.execute(); }) }) document.querySelectorAll(".engine").forEach(function (elem) { elem.addEventListener("click", function () { removeSelected() elem.classList.toggle('selected'); engineSelected = elem.dataset.engineid; teamEngineSelected = elem.dataset.teamengine document.querySelector(".engines-show").classList.remove("d-none") resetBarsEngines(elem) }) }) export function load_parts_stats(data) { currentPartsStats = data; performanceDraftStats = null; if (performanceDetailsMode !== "performance") { return; } applyPartsStatsToDom(data); } export function load_team_expertise(data) { currentTeamExpertise = data; expertiseDraftStats = null; updateExpertiseModeUi(); if (performanceDetailsMode !== "expertise") { return; } applyPartsStatsToDom(data); } export function gather_team_expertise_data() { let expertise = {}; document.querySelectorAll(".part-performance").forEach(function (elem) { const partType = elem.dataset.partid; if (!partType) return; expertise[partType] = {}; elem.querySelectorAll(".part-performance-stat").forEach(function (stat) { const statNum = stat.dataset.attribute; if (statNum === "-1" || statNum === "15") return; const input = stat.querySelector(".custom-input-number"); if (!input) return; const value = input.value.split(" ")[0]; expertise[partType][statNum] = value; }); }); return expertise; } export function load_parts_list(data) { for (let key in data) { let list = document.querySelector(`.part-performance[data-part='${key}'] .parts-list`) let partLoadouts = document.querySelector(`.part-performance[data-part='${key}']`) list.innerHTML = "" let index = 1; for (let part in data[key]) { let partElem = document.createElement("div") partElem.classList.add("one-part") if (index === 1) { partElem.classList.add("one-part-default") } let partTitle = document.createElement("div") partTitle.classList.add("one-part-title") let partName = document.createElement("div") partName.dataset.designId = data[key][part][0] partName.classList.add("one-part-name") let partNameText = abreviations_dict[teamSelected] + "-" + pars_abreviations[key] + "-" + index partName.innerText = partNameText let subtitle = document.querySelector(`.part-performance[data-part='${key}'] .part-subtitle`) subtitle.innerText = partNameText subtitle.dataset.editing = data[key][part][0] delete subtitle.dataset.performanceText; partTitle.appendChild(partName) add_partName_listener(partName, subtitle) let loadoutContainer = document.createElement("div") loadoutContainer.classList.add("fitted-icons") let n_parts = document.createElement("div") n_parts.classList.add("n-parts") n_parts.innerText = "x" + data[key][part][6] loadoutContainer.appendChild(n_parts) add_n_parts_buttons(loadoutContainer) let loadout1 = document.createElement("i") loadout1.classList.add("bi", "bi-check", "loadout-1") loadoutContainer.appendChild(loadout1) if (data[key][part][4] === 1) { loadout1.classList.add("fitted") let number = document.createElement("div") number.classList.add("number") number.innerText = "1" loadout1.appendChild(number) partLoadouts.dataset.loadout1 = data[key][part][0] } loadout_listener(loadout1, "1", partLoadouts) let loadout2 = document.createElement("i") loadout2.classList.add("bi", "bi-check", "loadout-2") loadoutContainer.appendChild(loadout2) if (data[key][part][5] === 1) { loadout2.classList.add("fitted") let number = document.createElement("div") number.classList.add("number") number.innerText = "2" loadout2.appendChild(number) partLoadouts.dataset.loadout2 = data[key][part][0] } loadout_listener(loadout2, "2", partLoadouts) partTitle.appendChild(loadoutContainer) let posRelative = document.createElement("div") posRelative.classList.add("one-part-flag-and-text") if (data[key][part][1] !== data[key][part][2]) { let flag = document.createElement("img") flag.classList.add("one-part-flag") let code = data[key][part][3] let codeFlag = races_map[code] let flagSrc = codes_dict[codeFlag] flag.src = flagSrc flag.setAttribute("loading","lazy"); let flagName = document.createElement("div") flagName.classList.add("one-part-flag-title") flagName.innerText = races_names[code] posRelative.appendChild(flag) posRelative.appendChild(flagName) } else { posRelative.innerText = "BASE" } partElem.appendChild(partTitle) partElem.appendChild(posRelative) partElem.dataset.partid = part list.appendChild(partElem) if (index === data[key].length) { partName.classList.add("editing") } index++; } add_new_part_button(list) } updateExpertiseModeUi(); } function add_new_part_button(list) { let new_part_div = document.createElement("div") new_part_div.classList.add("new-part") let icon = document.createElement("i") let generalPart = list.parentNode icon.classList.add("bi", "bi-plus-circle") icon.textContent = "Add new part" new_part_div.appendChild(icon) list.appendChild(new_part_div) icon.addEventListener("click", function () { let previousPart = list.childNodes[list.childNodes.length - 2] let previous_name = previousPart.querySelector(".one-part-name").innerText let new_name = previous_name.split("-")[0] + "-" + previous_name.split("-")[1] + "-" + (parseInt(previous_name.split("-")[2]) + 1) let part = document.createElement("div") part.classList.add("one-part") let partTitle = document.createElement("div") partTitle.classList.add("one-part-title") let partName = document.createElement("div") partName.dataset.designId = actualMaxDesign partName.classList.add("one-part-name") partName.innerText = new_name let subtitle = list.parentNode.querySelector(`.part-subtitle`) subtitle.dataset.editing = "-1" actualMaxDesign += 1 subtitle.innerText = new_name partTitle.appendChild(partName) let parts = list.querySelectorAll(".one-part") parts.forEach(function (part) { part.querySelector(".one-part-name").classList.remove("editing") }) add_partName_listener(partName, subtitle, "new") let loadoutContainer = document.createElement("div") loadoutContainer.classList.add("fitted-icons") let n_parts = document.createElement("div") n_parts.classList.add("n-parts") n_parts.innerText = "x0" loadoutContainer.appendChild(n_parts) add_n_parts_buttons(loadoutContainer) let loadout1 = document.createElement("i") loadout1.classList.add("bi", "bi-check", "loadout-1") loadoutContainer.appendChild(loadout1) loadout_listener(loadout1, "1", generalPart) let loadout2 = document.createElement("i") loadout2.classList.add("bi", "bi-check", "loadout-2") loadoutContainer.appendChild(loadout2) loadout_listener(loadout2, "2", generalPart) partTitle.appendChild(loadoutContainer) part.appendChild(partTitle) list.insertBefore(part, new_part_div) partName.classList.add("editing") new_part_div.remove() }) } function add_n_parts_buttons(loadoutContainer) { let buttonsContainer = document.createElement("div") buttonsContainer.classList.add("n-parts-buttons") let up = document.createElement("i") up.classList.add("bi", "bi-chevron-up", "new-augment-button") let down = document.createElement("i") down.classList.add("bi", "bi-chevron-down", "new-augment-button") buttonsContainer.appendChild(up) buttonsContainer.appendChild(down) up.addEventListener("click", function () { let n_parts = loadoutContainer.querySelector(".n-parts") let n = parseInt(n_parts.innerText.split("x")[1]) n += 1 n_parts.innerText = "x" + n }) down.addEventListener("click", function () { let fitted_parts = loadoutContainer.parentNode.querySelectorAll(".fitted") let fitted_parts_numb = fitted_parts.length let n_parts = loadoutContainer.querySelector(".n-parts") let n = parseInt(n_parts.innerText.split("x")[1]) if (n > fitted_parts_numb) { n -= 1 if (n < 0) { n = 0 } n_parts.innerText = "x" + n } else { fitted_parts.forEach(function (part) { let errorClass = "" if (part.classList.contains("loadout-1")) { errorClass = "loadout-1-error"; } else if (part.classList.contains("loadout-2")) { errorClass = "loadout-2-error"; } part.classList.add(errorClass); setTimeout(() => { part.classList.remove(errorClass); }, 500); }) } }) loadoutContainer.appendChild(buttonsContainer) } export function load_one_part(data) { let key = Object.keys(data)[0] if (!currentPartsStats) { currentPartsStats = {}; } currentPartsStats[key] = data[key]; performanceDraftStats = null; if (performanceDetailsMode !== "performance") { return; } applyPartsStatsToDom(data); } function add_partName_listener(div, subtitle, type = "old") { div.addEventListener("click", function () { if (performanceDetailsMode === "expertise") { return; } if (type === "new") { subtitle.dataset.editing = -1 } else { subtitle.dataset.editing = div.dataset.designId } subtitle.innerText = div.innerText let parts = div.parentNode.parentNode.parentNode.querySelectorAll(".one-part") parts.forEach(function (part) { part.querySelector(".one-part-name").classList.remove("editing") }) div.classList.add("editing") if (type === "old") { const command = new Command("partRequest", { designID: div.dataset.designId}); command.execute(); } }) } function loadout_listener(icon, loadout_n, partTitle) { icon.addEventListener("click", function () { let part_design = icon.parentNode.parentNode.querySelector(".one-part-name").dataset.designId; let n_parts_elem = icon.parentNode.querySelector(".n-parts"); let n_parts = n_parts_elem.innerText.split("x")[1]; let parts_fitted = icon.parentNode.parentNode.querySelectorAll(".fitted").length; if (parts_fitted < n_parts) { partTitle.dataset[`loadout${loadout_n}`] = part_design; if (loadout_n === "1") { let oldFitted = partTitle.querySelector(".loadout-1.fitted"); if (oldFitted) { oldFitted.classList.remove("fitted"); oldFitted.querySelector(".number").remove(); } icon.classList.toggle("fitted"); let number = document.createElement("div"); number.classList.add("number"); number.innerText = "1"; icon.appendChild(number); } else { let oldFitted = partTitle.querySelector(".loadout-2.fitted"); if (oldFitted) { oldFitted.classList.remove("fitted"); oldFitted.querySelector(".number").remove(); } icon.classList.toggle("fitted"); let number = document.createElement("div"); number.classList.add("number"); number.innerText = "2"; icon.appendChild(number); } } else { n_parts_elem.classList.add("n-parts-error"); setTimeout(() => { n_parts_elem.classList.remove("n-parts-error"); }, 500); } }); } document.querySelector("#fitButton").addEventListener("click", function () { let data = { command: "fitParts", teamID: teamSelected } //pending }) document.querySelectorAll(".part-performance-title .redesigned-chevron").forEach(function (elem) { elem.addEventListener("click", function () { elem.classList.toggle("clicked") let generalPart = elem.parentNode.parentNode elem.parentNode.querySelector(".part-buttons").classList.toggle("d-none") if (elem.classList.contains("clicked")) { generalPart.querySelector(".part-performance-stats").classList.add("hidden") } else { generalPart.querySelector(".part-performance-stats").classList.remove("hidden") } }) }) function buildHoldOptions(input, extra = {}) { const min = parseFloat(input.min); const max = parseFloat(input.max); const isEngineStat = !!input.closest(".engine-performance-stat"); const hasMin = input.min !== ""; const hasMax = input.max !== ""; const format = extra.format ?? ((val) => ( val.toFixed(2) )); const opts = { ...extra, format }; if (hasMin) { opts.min = min; } if (hasMax) { opts.max = max; } if (isEngineStat && !hasMin) { opts.min = 0; } if (isEngineStat && !hasMax) { opts.max = 100; } return opts; } document.querySelector(".performance-show").querySelectorAll(".part-name-buttons .bi-plus.new-augment-button").forEach(function (elem) { const part = elem.closest(".part-performance"); if (!part) return; const inputs = part.querySelectorAll(".custom-input-number"); inputs.forEach(function (input) { const increment = input.max === "100" ? 0.5 : 0.025; attachHold(elem, input, increment, buildHoldOptions(input)); }); }); document.querySelector(".performance-show").querySelectorAll(".part-name-buttons .bi-dash.new-augment-button").forEach(function (elem) { const part = elem.closest(".part-performance"); if (!part) return; const inputs = part.querySelectorAll(".custom-input-number"); inputs.forEach(function (input) { const increment = input.max === "100" ? -0.5 : -0.025; attachHold(elem, input, increment, buildHoldOptions(input)); }); }); document.querySelector(".performance-show").querySelectorAll(".stat-number .bi-plus.new-augment-button").forEach(button => { const input = button.parentNode.querySelector(".custom-input-number"); if (!input) return; attachHold(button, input, 0.01, buildHoldOptions(input)); }); document.querySelector(".performance-show").querySelectorAll(".stat-number .bi-dash.new-augment-button").forEach(button => { const input = button.parentNode.querySelector(".custom-input-number"); if (!input) return; attachHold(button, input, -0.01, buildHoldOptions(input)); }); document.querySelector(".engines-show").querySelectorAll(".stat-number .bi-plus.new-augment-button").forEach(button => { const stat = button.closest(".engine-performance-stat"); const input = button.parentNode.querySelector(".custom-input-number"); const bar = stat ? stat.querySelector(".engine-performance-progress") : null; if (!input) return; attachHold(button, input, 0.5, buildHoldOptions(input, { onChange: (val) => { setBarWidth(bar, val); } })); }); document.querySelector(".engines-show").querySelectorAll(".stat-number .bi-dash.new-augment-button").forEach(button => { const stat = button.closest(".engine-performance-stat"); const input = button.parentNode.querySelector(".custom-input-number"); const bar = stat ? stat.querySelector(".engine-performance-progress") : null; if (!input) return; attachHold(button, input, -0.5, buildHoldOptions(input, { onChange: (val) => { setBarWidth(bar, val); } })); }); document.querySelector(".performance-show").querySelectorAll(".new-or-existing-part div").forEach(function (elem) { elem.addEventListener("click", function () { let parent = elem.parentNode; let options = parent.querySelectorAll("div"); options.forEach(function (option) { option.classList.remove("active-part"); }) elem.classList.add("active-part"); parent.parentNode.parentNode.dataset.new = elem.dataset.new; }) }) const performanceGraphButton = document.getElementById("performanceGraphButton"); const performanceGraphIcon = performanceGraphButton.querySelector("i"); const performanceGraphText = performanceGraphButton.querySelector("span"); const performanceOverview = document.getElementById("performanceOverview"); const performanceExpertiseButton = document.getElementById("performanceExpertiseButton"); const performanceAnnotationsToggleInput = document.getElementById("performanceAnnotationsToggle"); if (performanceAnnotationsToggleInput) { performanceAnnotationsToggle = performanceAnnotationsToggleInput.checked; performanceAnnotationsToggleInput.addEventListener("change", function () { performanceAnnotationsToggle = performanceAnnotationsToggleInput.checked; if (!performanceGraph?.options?.plugins?.annotation) return; if (!Array.isArray(performanceGraph?.data?.labels)) return; applyAduoUpgradeAnnotations(currentData?.[2], currentData?.[1], performanceGraph.data.labels.length); performanceGraph.update(); }); } function setPerformanceView(view) { performanceView = view; viewingGraph = view === "graph"; performanceGraphButton.classList.add("active"); if (view === "graph") { performanceGraphIcon.className = "bi bi-graph-up"; performanceGraphText.textContent = "Graph"; } else if (view === "details") { performanceGraphIcon.className = "bi bi-list-ul"; performanceGraphText.textContent = "Details"; } else { performanceGraphIcon.className = "bi bi-grid-3x2-gap"; performanceGraphText.textContent = "Overview"; } document.querySelector("#performanceGraph").classList.toggle("d-none", view !== "graph"); document.querySelector(".teams-show").classList.toggle("d-none", view !== "details"); performanceOverview.classList.toggle("d-none", view !== "overview"); if (performanceExpertiseButton) { performanceExpertiseButton.classList.toggle("d-none", view !== "details" || teamsEngine !== "teams"); } document.querySelector(".save-button").classList.toggle("d-none", view !== "details"); if (view === "details") { first_show_animation(); } if (view === "overview") { load_overview(); } } if (performanceExpertiseButton) { performanceExpertiseButton.addEventListener("click", function () { const next = performanceDetailsMode === "performance" ? "expertise" : "performance"; setPerformanceDetailsMode(next); }); } function createOverviewCard(attributeConfig) { let card = document.createElement("div"); card.classList.add("overview-card"); let title = document.createElement("div"); title.classList.add("overview-card-title", "bold-font"); title.textContent = attributeConfig.label; if (attributeConfig.key === "brake_cooling" && game_version === 2024) { title.textContent = "Tyre preservation"; } card.appendChild(title); let teamsContainer = document.createElement("div"); teamsContainer.classList.add("overview-card-teams"); let teamsData = []; document.querySelectorAll("#teamsDiv .team-performance").forEach(function (teamElem) { let teamId = teamElem.dataset.teamid; let sourceBar = teamElem.querySelector(".performance-bar-progress"); if (!sourceBar) { return; } let teamRow = document.createElement("div"); teamRow.classList.add("overview-team", "bold-font"); if (teamElem.classList.contains("d-none")) { teamRow.classList.add("d-none"); } let carTitle = document.createElement("div"); carTitle.classList.add("car-title"); let leftContainer = document.createElement("div"); leftContainer.classList.add("overview-team-left"); let rank = document.createElement("span"); rank.classList.add("overview-team-rank"); leftContainer.appendChild(rank); let teamName = document.createElement("span"); teamName.className = teamElem.querySelector(".team-title-name").className; teamName.textContent = teamElem.querySelector(".team-title-name").textContent; leftContainer.appendChild(teamName); carTitle.appendChild(leftContainer); let teamValue = document.createElement("span"); teamValue.classList.add("overview-team-value"); carTitle.appendChild(teamValue); let performanceBar = document.createElement("div"); performanceBar.classList.add("performance-bar"); let progressBar = document.createElement("div"); progressBar.className = sourceBar.className; performanceBar.appendChild(progressBar); teamRow.appendChild(carTitle); teamRow.appendChild(performanceBar); let sourceKey = attributeConfig.source || attributeConfig.key; let value = parseFloat(sourceBar.dataset[sourceKey] || 0); setBarWidth(progressBar, value); teamValue.textContent = value.toFixed(2) + " %"; teamRow.dataset.teamid = teamId; teamRow.dataset.attribute = attributeConfig.key; teamsData.push({ teamRow: teamRow, teamId: teamId, value: value, isHidden: teamElem.classList.contains("d-none"), rank: rank }); }); let visibleTeams = teamsData.filter(t => !t.isHidden); visibleTeams.sort(function (a, b) { if (a.value === b.value) { return Number(a.teamId) - Number(b.teamId); } return b.value - a.value; }); const visibleCount = visibleTeams.length; visibleTeams.forEach(function (entry, index) { entry.rank.textContent = String(index + 1); teamsContainer.appendChild(entry.teamRow); }); teamsData.filter(t => t.isHidden).forEach(function (entry) { entry.rank.textContent = ""; teamsContainer.appendChild(entry.teamRow); }); card.appendChild(teamsContainer); return card; } function load_overview() { if (!performanceOverview) { return; } performanceOverview.innerHTML = ""; overviewAttributes.forEach(function (attributeConfig) { performanceOverview.appendChild(createOverviewCard(attributeConfig)); }); } document.querySelector("#performanceGraphButton").addEventListener("click", function () { if (performanceView === "graph") { setPerformanceView("details"); } else if (performanceView === "details") { setPerformanceView("overview"); } else { removeSelected(); setPerformanceView("graph"); } }) setPerformanceView("graph"); updatePerformanceExpertiseButton(); updateExpertiseModeUi(); document.querySelectorAll(".part-performance-title .bi-chevron-up").forEach(function (elem) { elem.addEventListener("click", function () { let title = elem.parentNode.parentNode let list = title.parentNode.querySelector(".parts-list") let partEditing = list.querySelector('.one-part-name.editing').parentNode.parentNode let newPart = partEditing.previousElementSibling if (!newPart) { let lastValidPart = list.lastElementChild; while (lastValidPart && lastValidPart.classList.contains('new-part')) { lastValidPart = lastValidPart.previousElementSibling; } newPart = lastValidPart; } newPart.querySelector(".one-part-name").click() }) }) document.querySelectorAll(".part-performance-title .bi-chevron-down").forEach(function (elem) { elem.addEventListener("click", function () { let title = elem.parentNode.parentNode; let list = title.parentNode.querySelector(".parts-list"); let partEditing = list.querySelector('.one-part-name.editing').parentNode.parentNode; let newPart = partEditing.nextElementSibling; // Si el siguiente es 'new-part', nos movemos al primero if (newPart && newPart.classList.contains('new-part')) { newPart = list.firstElementChild; } // Simulamos el click en el nuevo elemento encontrado (si es válido) if (newPart) { newPart.querySelector(".one-part-name").click(); } }); }); /** * Puts the bars of the engine to their appropiate values * @param {div} div element of the dom that contains the stats of the engine */ function resetBarsEngines(div) { let statsString = div.dataset.stats var statsArray = statsString.split(' ').map(function (item) { return parseFloat(item, 10) / 10; }); document.querySelector(".engines-show").querySelectorAll(".custom-progress").forEach(function (elem, index) { elem.dataset.progress = statsArray[index] manage_bar(elem, elem.dataset.progress) }) } /** * resets all bars to 0 */ function resetBars() { document.querySelectorAll(".custom-progress").forEach(function (elem) { elem.dataset.progress = 0 manage_bar(elem, elem.dataset.progress) }) } function add_custom_engine(name, stats) { let generalEngineDiv = document.createElement("div") let engineTitle = document.createElement("input") engineTitle.type = "text" if (name !== "") { engineTitle.value = name } else { engineTitle.value = "New Engine" } let engineCount = document.querySelectorAll(".custom-engines-div > div").length; let engineStatsId = `engineStats${engineCount + 1}`; let engineStats = document.createElement("div") let caret = document.createElement("i") let trash = document.createElement("i") trash.classList.add("bi", "bi-trash") caret.classList.add("redesigned-chevron", "clicked") generalEngineDiv.classList.add("engine-performance") engineTitle.classList.add("engine-performance-title") engineStats.classList.add("engine-performance-stats", "collapse", "show") engineStats.id = engineStatsId caret.addEventListener("click", function () { caret.classList.toggle("clicked") }) trash.addEventListener("click", function () { generalEngineDiv.remove() }) caret.setAttribute("data-bs-toggle", "collapse"); caret.setAttribute("data-bs-target", `#${engineStatsId}`); for (let [key, value] of engine_stats_dict) { if ((game_version === 2024 && key !== 11 && key !== 12) || game_version === 2023) { let stat = document.createElement("div") stat.classList.add("engine-performance-stat") stat.dataset.attribute = key let statTitle = document.createElement("div") statTitle.classList.add("part-performance-stat-title") statTitle.innerText = value + " "; const statUnit = document.createElement("span") statUnit.classList.add("text-secondary") statUnit.innerText = "%" statTitle.appendChild(statUnit) let stat_number = document.createElement("div") stat_number.classList.add("stat-number") stat_number.innerHTML = ' ' let input = stat_number.querySelector(".custom-input-number"); let bar = document.createElement("div") bar.classList.add("engine-performance-bar") let bar_progress = document.createElement("div") bar_progress.classList.add("engine-performance-progress") if (stats[key.toString()] !== undefined) { input.value = Number(stats[key]).toFixed(1); setBarWidth(bar_progress, stats[key]); } else { input.value = "50.0"; } stat.appendChild(statTitle) stat.appendChild(stat_number) bar.appendChild(bar_progress) stat.appendChild(bar) engineStats.appendChild(stat) let less = stat_number.querySelector(".bi-dash.new-augment-button"); let plus = stat_number.querySelector(".bi-plus.new-augment-button"); const holdOptions = buildHoldOptions(input, { onChange: (val) => { setBarWidth(bar_progress, val); } }); attachHold(less, input, -0.5, holdOptions); attachHold(plus, input, 0.5, holdOptions); } } const blankSpace = document.createElement("div"); blankSpace.classList.add("blank-engine-space"); engineStats.appendChild(blankSpace); generalEngineDiv.appendChild(engineTitle) generalEngineDiv.appendChild(engineStats) generalEngineDiv.appendChild(caret) generalEngineDiv.appendChild(trash) document.querySelector(".custom-engines-div").appendChild(generalEngineDiv) } function wireEngineStatButtons(container) { container.querySelectorAll(".engine-performance-stat").forEach(function (stat) { const input = stat.querySelector(".custom-input-number") const bar = stat.querySelector(".engine-performance-progress") if (!input) return const holdOptions = buildHoldOptions(input, { onChange: (val) => { setBarWidth(bar, val) } }) const plus = stat.querySelector(".bi-plus.new-augment-button") const less = stat.querySelector(".bi-dash.new-augment-button") if (plus) { attachHold(plus, input, 0.5, holdOptions) } if (less) { attachHold(less, input, -0.5, holdOptions) } }) } function createCustomEngineCard(engineId, name, stats) { const engineDiv = document.createElement("div") engineDiv.classList.add("engine-performance", "custom-engine-card") engineDiv.dataset.engineid = engineId engineDiv.dataset.customEngine = "true" const title = document.createElement("div") title.classList.add("engine-performance-title", "custom-engine-title") const logo = document.createElement("img") logo.classList.add("engine-performance-logo") logo.src = getEngineLogoSrc(name) logo.alt = `${name || "Custom engine"} logo` const nameInput = document.createElement("input") nameInput.type = "text" nameInput.classList.add("custom-engine-name") nameInput.value = name || "New Engine" nameInput.addEventListener("input", function () { logo.src = getEngineLogoSrc(nameInput.value) const engineDropdownItem = document.querySelector(`#engineMenu a.custom-engine[data-engine="${engineId}"]`) if (engineDropdownItem) { engineDropdownItem.innerText = nameInput.value } }) title.appendChild(logo) title.appendChild(nameInput) engineDiv.appendChild(title) const customFlag = document.createElement("i") customFlag.classList.add("bi", "bi-sliders2", "custom-engine-flag") customFlag.setAttribute("title", "Delete custom engine") customFlag.addEventListener("mouseenter", function () { customFlag.classList.remove("bi-sliders2") customFlag.classList.add("bi-trash") }) customFlag.addEventListener("mouseleave", function () { customFlag.classList.remove("bi-trash") customFlag.classList.add("bi-sliders2") }) customFlag.addEventListener("click", async function (e) { e.preventDefault() e.stopPropagation() const ok = await confirmModal({ title: "Delete custom engine", body: "Are you sure you want to delete this custom engine? Any team using it will be assigned a different engine.", confirmText: "Delete", cancelText: "Cancel" }) if (!ok) return const command = new Command("deleteCustomEngine", { engineId: engineId }) command.execute() }) engineDiv.appendChild(customFlag) const engineStats = document.createElement("div") engineStats.classList.add("engine-performance-stats") for (let [key, value] of engine_stats_dict) { const stat = document.createElement("div") stat.classList.add("engine-performance-stat") if (key === 11 || key === 12) { stat.classList.add("engine24", "d-none") } stat.dataset.attribute = key const statTitle = document.createElement("div") statTitle.classList.add("part-performance-stat-title") statTitle.innerText = value + " " const unit = document.createElement("span") unit.classList.add("unit-measure", "bold-font") unit.innerText = "%" statTitle.appendChild(unit) const statNumber = document.createElement("div") statNumber.classList.add("stat-number") const less = document.createElement("i") less.classList.add("bi", "bi-dash", "new-augment-button", "transparent") const input = document.createElement("input") input.type = "text" input.classList.add("custom-input-number") const plus = document.createElement("i") plus.classList.add("bi", "bi-plus", "new-augment-button", "transparent") statNumber.appendChild(less) statNumber.appendChild(input) statNumber.appendChild(plus) const bar = document.createElement("div") bar.classList.add("engine-performance-bar") const barProgress = document.createElement("div") barProgress.classList.add("engine-performance-progress") bar.appendChild(barProgress) const rawValue = stats?.[String(key)] ?? stats?.[key] const numericValue = rawValue !== undefined ? Number(rawValue) : 50 input.value = numericValue.toFixed(1) setBarWidth(barProgress, numericValue) stat.appendChild(statTitle) stat.appendChild(statNumber) stat.appendChild(bar) engineStats.appendChild(stat) } engineDiv.appendChild(engineStats) wireEngineStatButtons(engineDiv) return engineDiv } function renderCustomEnginesInList(engines) { const enginesContainer = document.getElementById("enginesPerformance") if (!enginesContainer) return enginesContainer.querySelectorAll(".engine-performance.custom-engine-card").forEach(function (elem) { elem.remove() }) engines.forEach(function (engine) { const engineId = engine[0] const engineStats = engine[1] || {} const engineName = engine[2] || "" enginesContainer.appendChild(createCustomEngineCard(engineId, engineName, engineStats)) }) } function getNextCustomEngineId() { const ids = Array.from(document.querySelectorAll("#enginesPerformance .engine-performance[data-custom-engine=\"true\"]")) .map((elem) => Number(elem.dataset.engineid)) if (!ids.length) return 14 let nextId = Math.max(...ids) + 3 const used = new Set(ids) while (used.has(nextId)) { nextId += 3 } return nextId } const addCustomEngineButton = document.getElementById("customEngines") if (addCustomEngineButton) { addCustomEngineButton.addEventListener("click", function () { const enginesContainer = document.getElementById("enginesPerformance") if (!enginesContainer) return enginesContainer.appendChild(createCustomEngineCard(getNextCustomEngineId(), "", {})) enginesContainer.scrollTop = enginesContainer.scrollHeight }) } export function gather_custom_engines_data() { const engines = document.querySelectorAll("#enginesPerformance .engine-performance[data-custom-engine=\"true\"]") let enginesData = {} engines.forEach(function (engine) { const engineID = engine.dataset.engineid const nameInput = engine.querySelector(".custom-engine-name") const engineName = String(nameInput?.value || "").trim().toLowerCase() let engineStats = {} engine.querySelectorAll(".engine-performance-stat").forEach(function (stat) { let attribute = stat.dataset.attribute let value = stat.querySelector(".custom-input-number").value.split(" ")[0] engineStats[attribute] = value }) enginesData[engineID] = { stats: engineStats, name: engineName || "new engine" } }) return enginesData } export function load_custom_engines(data) { const engines = data || [] customEnginesCopy = data const engineDropdown = document.querySelector("#engineMenu") if (engineDropdown) { engineDropdown.querySelectorAll("a.custom-engine").forEach(function (elem) { elem.remove() }) engines.forEach(function (engine) { let engineOption = document.createElement("a") engineOption.classList.add("redesigned-dropdown-item", "custom-engine") engineOption.innerText = engine[2] engineOption.dataset.engine = engine[0] engineOption.href = "#" engineDropdown.appendChild(engineOption) engineOption.addEventListener("click", function () { let engineid = engineOption.dataset.engine let engineName = engineOption.innerText document.querySelector("#engineLabel").innerText = engineName document.querySelector("#engineButton").dataset.value = engineid }) }) } renderCustomEnginesInList(engines) } /** * Manages the progression of the bars * @param {div} bar bar that is about to be edited * @param {int} progress number that determines the progress of the bar */ function manage_bar(bar, progress) { if (bar.dataset.type === "engine") { let whiteDiv = bar.querySelector(".white-part") let newProgress = progress * 10 let newWidth = 0 + newProgress + "%" whiteDiv.style.width = newWidth; } else { let grayDiv = bar.querySelector(".gray-part") let greenDiv = bar.querySelector(".green-part") if (progress == 0) { grayDiv.style.width = "100%" greenDiv.style.width = "0%" bar.parentNode.querySelector(".performance-data").className = "performance-data bold-font" } else if (progress > 0) { grayDiv.style.width = "100%" let newProgress = progress * 10 let newWidth = 0 + newProgress + "%" greenDiv.style.width = newWidth; bar.parentNode.querySelector(".performance-data").className = "performance-data bold-font positive" } else if (progress < 0) { greenDiv.style.width = "0%" let newProgress = progress * 10 let newWidth = 100 + newProgress + "%" grayDiv.style.width = newWidth; bar.parentNode.querySelector(".performance-data").className = "performance-data bold-font negative" } } bar.parentNode.querySelector(".performance-data").innerHTML = progress * 10 + "%" } export function reload_performance_graph() { if (typeof performanceGraph !== 'undefined' && performanceGraph !== null) { performanceGraph.destroy(); load_performance_graph(currentData); } } export function load_performance_graph(data) { currentData = data const aduoUpgradeRaceIds = Array.isArray(data?.[2]) ? data[2] : []; let labelsArray = [] data[1].forEach(function (elem) { labelsArray.push(races_names[elem[2]]) }) labelsArray.unshift("") if (typeof performanceGraph !== 'undefined' && performanceGraph !== null) { performanceGraph.destroy(); } createPerformanceChart(labelsArray) applyAduoUpgradeAnnotations(aduoUpgradeRaceIds, data?.[1], labelsArray.length) performanceGraph.update() let teamPerformances = {}; // Inicializar un array vacío para cada equipo for (let i = 1; i <= 10; i++) { teamPerformances[i] = []; } teamPerformances[32] = []; let minValue = Number.POSITIVE_INFINITY; let maxValue = Number.NEGATIVE_INFINITY; let performances = [...data[0]] performances.forEach(race => { for (let team in race) { let value = race[team]; teamPerformances[team].push(value); if (value < minValue) { minValue = value; } if (value > maxValue) { maxValue = value; } } }); let yAxisMin = minValue - 5; let yAxisMax = maxValue + 5; for (let team in teamPerformances) { let color = get_colors_dict()[team + "0"]; let data = teamPerformances[team]; performanceGraph.data.datasets.push({ label: combined_dict[team], data: data, borderColor: color, backgroundColor: color, pointRadius: 0, fill: false, tension: 0.1, pointHitRadius: 7 }); } performanceGraph.options.scales.y.min = yAxisMin; performanceGraph.options.scales.y.max = yAxisMax; performanceGraph.update(); } function applyAduoUpgradeAnnotations(raceIds, races, labelCount) { if (!performanceGraph?.options?.plugins?.annotation) return; const ids = Array.isArray(raceIds) ? raceIds.map(r => Number(r)).filter(r => Number.isFinite(r) && r > 0) : []; const raceIdToLabelIndex = new Map(); if (Array.isArray(races)) { for (let i = 0; i < races.length; i++) { const raceId = Number(races[i]?.[0]); if (!Number.isFinite(raceId)) continue; raceIdToLabelIndex.set(raceId, i + 1); // +1 because labelsArray.unshift("") } } const annotations = {}; for (const raceId of ids) { const labelIndex = raceIdToLabelIndex.get(raceId); if (!labelIndex || labelIndex <= 0 || labelIndex >= labelCount) continue; const boundaryIndex = labelIndex - 1; // La línea se dibuja antes del índice de la etiqueta correspondiente if (boundaryIndex <= 0) continue; annotations[`aduo_engine_${raceId}`] = { type: 'line', xMin: boundaryIndex, xMax: boundaryIndex, display: performanceAnnotationsToggle, borderColor: theme_colors[selectedTheme]?.engine_upgrade_line || 'rgba(253, 224, 107, 0.8)', borderWidth: 2, borderDash: [6, 6], drawTime: 'beforeDatasetsDraw' }; } performanceGraph.options.plugins.annotation.annotations = annotations; } /** * Creates the head to head race chart * @param {Array} labelsArray array with all the labels for the races */ function createPerformanceChart(labelsArray) { const dataD = { labels: labelsArray, }; performanceGraph = new Chart( document.getElementById('performanceGraph'), { type: 'line', data: dataD, options: { responsive: true, maintainAspectRatio: false, animation: false, interaction: { mode: 'index' }, layout: { padding: { top: 25, right: 25, boottom: 20, left: 10 } }, scales: { x: { grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" } } }, y: { min: 0, max: 100, grid: { color: theme_colors[selectedTheme].grid }, ticks: { color: theme_colors[selectedTheme].labels, font: { family: "Formula1Bold" }, callback: function (value) { return value.toFixed(1); // Mostrar solo un decimal } } } }, plugins: { datalabels: { display: false }, annotation: { annotations: {} }, legend: { labels: { boxHeight: 2, boxWidth: 25, color: theme_colors[selectedTheme].labels, font: { family: "Formula1" } }, display: false, }, tooltip: { titleFont: { family: 'Formula1Bold', size: 16 }, bodyFont: { family: 'Formula1', size: 14 } } } } } ); } ================================================ FILE: src/js/frontend/recentsManager.js ================================================ import { get, set } from 'idb-keyval'; const DB_NAME = "SaveEditorDB"; const STORE_NAME = "recentFileHandles"; const DB_VERSION = 1; function openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains(STORE_NAME)) { db.createObjectStore(STORE_NAME, { keyPath: "name" }); } }; request.onsuccess = (e) => resolve(e.target.result); request.onerror = (e) => reject("Error opening DB"); }); } export async function saveHandleToRecents(handle) { let recents = (await get('recentFiles')) || []; recents = recents.filter(r => r.name !== handle.name); recents.unshift({ name: handle.name, handle: handle, lastOpened: new Date() }); await set('recentFiles', recents); } export async function getRecentHandles() { return (await get('recentFiles')) || []; } export async function removeRecentHandle(name) { let recents = (await get('recentFiles')) || []; recents = recents.filter(r => r.name !== name); await set('recentFiles', recents); } ================================================ FILE: src/js/frontend/regulations.js ================================================ import { attachHold } from "./renderer"; let regulationsState = null; const root = document.getElementById("regulations"); const spendingCapInput = document.getElementById("regSpendingCap"); const engineLimitInput = document.getElementById("regEngineLimit"); const ersLimitInput = document.getElementById("regErsLimit"); const gearboxLimitInput = document.getElementById("regGearboxLimit"); const doubleLastRacePointsToggle = document.getElementById("regDoubleLastRacePoints"); const fastestLapBonusPointToggle = document.getElementById("regFastestLapBonusPoint"); const polePositionBonusPointToggle = document.getElementById("regPolePositionBonusPoint"); const createPointSchemeButton = document.getElementById("regCreatePointScheme"); const createResourcePackageButton = document.getElementById("regCreateResourcePackage"); const pointSchemeDropdownButton = document.getElementById("regPointSchemeButton"); const pointSchemeMenu = document.getElementById("regPointSchemeMenu"); const pointSchemeBody = document.getElementById("regPointSchemeBody"); const resourcePackageDropdownButton = document.getElementById("regResourcePackageButton"); const resourcePackageMenu = document.getElementById("regResourcePackageMenu"); const resourcePackageBody = document.getElementById("regResourcePackageBody"); function getEnumChange(id) { return regulationsState?.enumChanges?.[id] ?? null; } function parseIntSafe(val, fallback = 0) { const n = Number.parseInt(String(val).replace(/[^\d-]/g, ""), 10); return n; } function formatMoney(val) { const n = Number(val); return n.toLocaleString("en-US"); } function getSchemeName(id) { const n = Number(id); if (n === 1) return "2010–Present"; if (n === 2) return "2003–2009"; if (n === 3) return "1991–2002"; return `Custom Scheme ${n}`; } function getPackageName(id) { return `Custom Package ${Number(id)}`; } function getSelectedPointScheme() { const row = getEnumChange("PointScheme"); const current = row?.CurrentValue ?? 1; return Number(current) || 1; } function getSelectedResourcePackage() { const row = getEnumChange("PartDevResourceLimit"); const current = row?.CurrentValue ?? 1; return Number(current) || 1; } function setDropdownLabel(btn, label) { const labelEl = btn?.querySelector(".dropdown-label"); if (labelEl) labelEl.textContent = label; } function updateMenus() { if (!regulationsState || !pointSchemeMenu || !resourcePackageMenu) return; const selectedScheme = getSelectedPointScheme(); const selectedPackage = getSelectedResourcePackage(); pointSchemeMenu.innerHTML = ""; Object.keys(regulationsState.pointSchemes || {}) .map(Number) .sort((a, b) => a - b) .forEach((id) => { const item = document.createElement("a"); item.className = "redesigned-dropdown-item"; item.dataset.value = String(id); item.style.cursor = "pointer"; item.textContent = getSchemeName(id); if (id === selectedScheme) { const check = document.createElement("i"); check.className = "bi bi-check"; item.appendChild(document.createTextNode(" ")); item.appendChild(check); } item.addEventListener( "click", () => { regulationsState.enumChanges.PointScheme.CurrentValue = id; setDropdownLabel(pointSchemeDropdownButton, getSchemeName(id)); updateMenus(); renderPointSchemeTable(); }, { once: true } ); pointSchemeMenu.appendChild(item); }); resourcePackageMenu.innerHTML = ""; Object.keys(regulationsState.partResources || {}) .map(Number) .sort((a, b) => a - b) .forEach((id) => { const item = document.createElement("a"); item.className = "redesigned-dropdown-item"; item.dataset.value = String(id); item.style.cursor = "pointer"; item.textContent = getPackageName(id); if (id === selectedPackage) { const check = document.createElement("i"); check.className = "bi bi-check"; item.appendChild(document.createTextNode(" ")); item.appendChild(check); } item.addEventListener( "click", () => { regulationsState.enumChanges.PartDevResourceLimit.CurrentValue = id; setDropdownLabel(resourcePackageDropdownButton, getPackageName(id)); updateMenus(); renderResourcePackageTable(); }, { once: true } ); resourcePackageMenu.appendChild(item); }); } function renderPointSchemeTable() { if (!regulationsState || !pointSchemeBody) return; const schemeId = getSelectedPointScheme(); const rows = regulationsState.pointSchemes?.[schemeId] || []; pointSchemeBody.innerHTML = ""; for (const row of rows) { const wrapper = document.createElement("div"); wrapper.className = "regulations-row"; const pos = document.createElement("div"); pos.className = "regulations-cell regulations-keycell"; pos.textContent = String(row.RacePos); const pointsCell = document.createElement("div"); pointsCell.className = "regulations-cell"; const input = document.createElement("input"); input.type = "number"; input.min = "0"; input.step = "1"; input.value = String(row.Points ?? 0); input.className = "custom-input-number"; input.addEventListener("input", () => { row.Points = parseIntSafe(input.value, 0); regulationsState.pointSchemes[schemeId] = rows; }); pointsCell.appendChild(input); wrapper.appendChild(pos); wrapper.appendChild(pointsCell); pointSchemeBody.appendChild(wrapper); } } function renderResourcePackageTable() { if (!regulationsState || !resourcePackageBody) return; const packageId = getSelectedResourcePackage(); const rows = regulationsState.partResources?.[packageId] || []; resourcePackageBody.innerHTML = ""; for (const row of rows) { const wrapper = document.createElement("div"); wrapper.className = "regulations-row"; const pos = document.createElement("div"); pos.className = "regulations-cell regulations-keycell"; pos.textContent = String(row.StandingPos); const windCell = document.createElement("div"); windCell.className = "regulations-cell"; const windInput = document.createElement("input"); windInput.type = "number"; windInput.min = "0"; windInput.step = "1"; windInput.value = String(row.WindTunnelBlocks ?? 0); windInput.className = "custom-input-number"; windInput.addEventListener("input", () => { row.WindTunnelBlocks = parseIntSafe(windInput.value, 0); regulationsState.partResources[packageId] = rows; }); windCell.appendChild(windInput); const cfdCell = document.createElement("div"); cfdCell.className = "regulations-cell"; const cfdInput = document.createElement("input"); cfdInput.type = "number"; cfdInput.min = "0"; cfdInput.step = "1"; cfdInput.value = String(row.CfdBlocks ?? 0); cfdInput.className = "custom-input-number"; cfdInput.addEventListener("input", () => { row.CfdBlocks = parseIntSafe(cfdInput.value, 0); regulationsState.partResources[packageId] = rows; }); cfdCell.appendChild(cfdInput); wrapper.appendChild(pos); wrapper.appendChild(windCell); wrapper.appendChild(cfdCell); resourcePackageBody.appendChild(wrapper); } } function initHoldControlsOnce() { if (!root || root.dataset.holdInit === "1") return; root.dataset.holdInit = "1"; const controls = [ { input: spendingCapInput, id: "SpendingCap", step: 1_000_000, format: (val) => formatMoney(val) }, { input: engineLimitInput, id: "EngineLimit", step: 1 }, { input: ersLimitInput, id: "ErsLimit", step: 1 }, { input: gearboxLimitInput, id: "GearboxLimit", step: 1 }, ]; if (spendingCapInput && spendingCapInput.dataset.moneyInit !== "1") { spendingCapInput.dataset.moneyInit = "1"; spendingCapInput.addEventListener("blur", () => { spendingCapInput.value = formatMoney(parseIntSafe(spendingCapInput.value, 0)); }); } for (const c of controls) { const container = c.input?.closest(".stat-number"); if (!container) continue; const minus = container.querySelector(".bi-dash.new-augment-button"); const plus = container.querySelector(".bi-plus.new-augment-button"); const change = getEnumChange(c.id); const min = change?.MinValue ?? 0; const max = change?.MaxValue ?? Number.POSITIVE_INFINITY; const holdOpts = { min, max }; if (c.format) holdOpts.format = c.format; if (plus) { attachHold(plus, c.input, c.step, holdOpts); } if (minus) { attachHold(minus, c.input, -c.step, holdOpts); } } } function createNewPointScheme() { if (!regulationsState) return; const ids = Object.keys(regulationsState.pointSchemes || {}).map(Number); const nextId = (ids.length ? Math.max(...ids) : 0) + 1; const baseLen = (regulationsState.pointSchemes?.[1] || []).length || 10; regulationsState.pointSchemes[nextId] = Array.from({ length: baseLen }, (_, i) => ({ RacePos: i + 1, Points: baseLen - i, })); regulationsState.enumChanges.PointScheme.CurrentValue = nextId; setDropdownLabel(pointSchemeDropdownButton, getSchemeName(nextId)); updateMenus(); renderPointSchemeTable(); } function createNewResourcePackage() { if (!regulationsState) return; const ids = Object.keys(regulationsState.partResources || {}).map(Number); const nextId = (ids.length ? Math.max(...ids) : 0) + 1; const baseLen = (regulationsState.partResources?.[1] || []).length || 10; regulationsState.partResources[nextId] = Array.from({ length: baseLen }, (_, i) => ({ StandingPos: i + 1, WindTunnelBlocks: 72, CfdBlocks: 72, })); regulationsState.enumChanges.PartDevResourceLimit.CurrentValue = nextId; setDropdownLabel(resourcePackageDropdownButton, getPackageName(nextId)); updateMenus(); renderResourcePackageTable(); } export function load_regulations(data) { if (!root) return; regulationsState = JSON.parse(JSON.stringify(data || {})); const required = [ "SpendingCap", "EngineLimit", "ErsLimit", "GearboxLimit", "DoubleLastRacePoints", "FastestLapBonusPoint", "PolePositionBonusPoint", "PointScheme", "PartDevResourceLimit", ]; for (const k of required) { if (!regulationsState.enumChanges?.[k]) { regulationsState.enumChanges = regulationsState.enumChanges || {}; regulationsState.enumChanges[k] = { CurrentValue: 0, MinValue: 0, MaxValue: 0 }; } } spendingCapInput.value = formatMoney(regulationsState.enumChanges.SpendingCap.CurrentValue ?? 0); engineLimitInput.value = String(regulationsState.enumChanges.EngineLimit.CurrentValue ?? 0); ersLimitInput.value = String(regulationsState.enumChanges.ErsLimit.CurrentValue ?? 0); gearboxLimitInput.value = String(regulationsState.enumChanges.GearboxLimit.CurrentValue ?? 0); doubleLastRacePointsToggle.checked = regulationsState.enumChanges.DoubleLastRacePoints.CurrentValue === 1; fastestLapBonusPointToggle.checked = regulationsState.enumChanges.FastestLapBonusPoint.CurrentValue === 1; polePositionBonusPointToggle.checked = regulationsState.enumChanges.PolePositionBonusPoint.CurrentValue === 1; setDropdownLabel(pointSchemeDropdownButton, getSchemeName(getSelectedPointScheme())); setDropdownLabel(resourcePackageDropdownButton, getPackageName(getSelectedResourcePackage())); initHoldControlsOnce(); updateMenus(); renderPointSchemeTable(); renderResourcePackageTable(); } export function gather_regulations_data() { if (!regulationsState) return null; regulationsState.enumChanges.SpendingCap.CurrentValue = parseIntSafe(spendingCapInput.value, 0); regulationsState.enumChanges.EngineLimit.CurrentValue = parseIntSafe(engineLimitInput.value, 0); regulationsState.enumChanges.ErsLimit.CurrentValue = parseIntSafe(ersLimitInput.value, 0); regulationsState.enumChanges.GearboxLimit.CurrentValue = parseIntSafe(gearboxLimitInput.value, 0); regulationsState.enumChanges.DoubleLastRacePoints.CurrentValue = doubleLastRacePointsToggle.checked ? 1 : 0; regulationsState.enumChanges.FastestLapBonusPoint.CurrentValue = fastestLapBonusPointToggle.checked ? 1 : 0; regulationsState.enumChanges.PolePositionBonusPoint.CurrentValue = polePositionBonusPointToggle.checked ? 1 : 0; return { enumChanges: regulationsState.enumChanges, pointSchemes: regulationsState.pointSchemes, partResources: regulationsState.partResources, }; } if (createPointSchemeButton) { createPointSchemeButton.addEventListener("click", () => { createNewPointScheme(); }); } if (createResourcePackageButton) { createResourcePackageButton.addEventListener("click", () => { createNewResourcePackage(); }); } ================================================ FILE: src/js/frontend/renderer.js ================================================ import { marked } from 'marked'; import DOMPurify from 'dompurify'; import { resetTeamEditing, fillLevels, longTermObj, originalCostCap, gather_team_data, gather_pit_crew, teamCod } from './teams'; import { resetViewer, generateYearsMenu, resetYearButtons, update_logo, setEngineAllocations, engine_names, new_drivers_table, new_teams_table, new_load_drivers_table, new_load_teams_table, addEngineName, deleteEngineName, reloadTables, populateSeasonReview, onSessionResultsFetched } from './seasonViewer'; import { combined_dict, abreviations_dict, codes_dict, logos_disc, mentality_to_global_menatality, difficultyConfig, default_dict, weightDifConfig, defaultDifficultiesConfig, defaultTurningPointsFrequencyPreset, turningPointsFrequencyLabels, themeToolbarLogos } from './config'; import { freeDriversDiv, insert_space, place_staff, remove_drivers, add_marquees_transfers, place_drivers, sortList, update_name, manage_modal, loadJuniorTeamDrivers, initFreeDriversElems } from './transfers'; import { load_calendar } from './calendar'; import { load_performance, load_performance_graph, load_attributes, manage_engineStats, load_cars, load_custom_engines, order_by, load_car_attributes, viewingGraph, load_parts_stats, load_parts_list, update_max_design, teamsEngine, load_one_part, teamSelected, gather_engines_data, gather_custom_engines_data, reload_performance_graph, load_team_expertise, gather_team_expertise_data, performanceDetailsMode } from './performance'; import { removeStatsDrivers, place_drivers_editStats, place_staff_editStats, typeOverall, setStatPanelShown, setTypeOverall, typeEdit, setTypeEdit, change_elegibles, getName, calculateOverall, listenersStaffGroups, initStatsDrivers, loadNumbers, loadRandomStaffDraft, isDraftProfileSelected, applyDraftForenameUpdate, applyDraftCountryLocale } from './stats'; import { resetH2H, hideComp, colors_dict, load_drivers_h2h, sprintsListeners, racePaceListener, qualiPaceListener, manage_h2h_bars, load_labels_initialize_graphs, reload_h2h_graphs, init_colors_dict, edit_colors_dict, setMidGrid, setMaxRaces, setRelativeGrid } from './head2head'; import { place_news, updateNewsYearsButton } from './news.js'; import { load_regulations, gather_regulations_data } from './regulations.js'; import { loadRecordsList, loadTeamRecordsList } from './seasonViewer'; import { resetStaffIDChanges, updateEditsWithModData } from '../backend/scriptUtils/modUtils.js'; import { dbWorker, handleDragEnter, handleDragLeave, handleDragOver, handleDrop, processSaveFile } from './dragFile'; import { Command } from "../backend/command.js"; import { saveAs } from "file-saver"; import members from "../../data/members.json" import { createTeamReplacers, logos_configs, pretty_names } from "./teamReplacements.js"; import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js"; import { getRecentHandles, saveHandleToRecents, removeRecentHandle } from './recentsManager.js'; import { initSeasonMods, syncAduoTpToggles, syncMods2025Dependencies, syncMods2026Dependencies, syncMods2026ApplyAllButtonState, updateMod2025Blocking, updateMod2026Blocking } from './seasonMods.js'; const driverTransferPill = document.getElementById("transferpill"); const editStatsPill = document.getElementById("statspill"); const CalendarPill = document.getElementById("calendarpill"); const regulationsPill = document.getElementById("regulationspill"); const carPill = document.getElementById("carpill"); const viewPill = document.getElementById("viewerpill"); const h2hPill = document.getElementById("h2hpill"); const constructorsPill = document.getElementById("constructorspill") const newsPill = document.getElementById("newspill") const modPill = document.getElementById("modpill") export const editorPill = document.getElementById("editorPill") export const gamePill = document.getElementById("gamePill") const patreonPill = document.getElementById("patreonPill") const driverTransferDiv = document.getElementById("driver_transfers"); const editStatsDiv = document.getElementById("edit_stats"); const customCalendarDiv = document.getElementById("custom_calendar"); const regulationsDiv = document.getElementById("regulations"); const carPerformanceDiv = document.getElementById("car_performance"); const viewDiv = document.getElementById("season_viewer"); const h2hDiv = document.getElementById("head2head_viewer"); const teamsDiv = document.getElementById("edit_teams"); const seasonModsDiv = document.getElementById("season_mods") const newsDiv = document.getElementById("news") const patchNotesBody = document.getElementById("patchNotesBody") const selectImageButton = document.getElementById('selectImage'); const patreonLoginButton = document.getElementById('patreonLoginButton'); const patreonLogoutButton = document.getElementById('patreonLogoutButton'); const patreonToolLoginButton = document.getElementById('patreonToolLoginButton'); const userToolButton = document.getElementById('userToolButton'); const saveFileButton = document.getElementById('saveFileButton'); const scriptsArray = [newsDiv, h2hDiv, viewDiv, driverTransferDiv, editStatsDiv, teamsDiv, customCalendarDiv, regulationsDiv, carPerformanceDiv, seasonModsDiv] initSeasonMods(); document.addEventListener("random-staff-requested", function (event) { const data = event.detail || {}; const command = new Command("fetchRandomStaffDraft", data); command.execute(); }); document.addEventListener("random-forename-requested", function (event) { const data = event.detail || {}; const command = new Command("fetchRandomDraftForename", data); command.execute(); }); document.addEventListener("draft-nationality-selected", function (event) { const data = event.detail || {}; const command = new Command("fetchCountryLocaleForCode", data); command.execute(); }); const dropDownMenu = document.getElementById("dropdownMenu"); const notificationPanel = document.getElementById("notificationPanel"); const logButton = document.getElementById("logFileButton"); const patreonLogo = document.querySelector(".footer .bi-custom-patreon"); const patreonSlideUp = document.querySelector(".patreon-slide-up"); const slideUpClose = document.getElementById("patreonSlideUpClose") const patreonUnlockables = document.querySelector(".patreon-unlockables") const downloadSaveButton = document.querySelector(".download-save-button") const downloadSaveProgress = document.getElementById("downloadSaveProgress"); const downloadSaveProgressFill = document.getElementById("downloadSaveProgressFill"); const patreonThemes = document.querySelector(".patreon-themes"); const status = document.querySelector(".status-info") const updateInfo = document.querySelector(".update-info") const turningPointsFrequencyConfig = document.getElementById("turningPointsFrequencyConfig"); const turningPointsFrequencySlider = document.getElementById("turningPointsFrequencySlider"); const turningPointsFrequencyLabel = document.getElementById("turningPointsFrequencyLabel"); const forceEditorMinimapColorsToggle = document.getElementById("forceEditorMinimapColorsToggle"); function updateTurningPointsFrequencyUI() { if (!turningPointsFrequencySlider || !turningPointsFrequencyLabel) return; const idx = parseInt(turningPointsFrequencySlider.value, 10); turningPointsFrequencySlider.value = String(idx); turningPointsFrequencyLabel.textContent = turningPointsFrequencyLabels[idx]; const directionClass = idx === defaultTurningPointsFrequencyPreset ? "tp-default" : idx > defaultTurningPointsFrequencyPreset ? "tp-more" : "tp-less"; turningPointsFrequencyLabel.className = `option-state ${directionClass}`; } const fileInput = document.getElementById('fileInput'); const saveFileInput = document.getElementById('saveFileInput'); const noNotifications = ["Custom Engines fetched", "Cars fetched", "Part values fetched", "Parts stats fetched", "Team expertise fetched", "Expertise updated", "24 Year", "Game Year", "Performance fetched", "Season performance fetched", "Config", "ERROR", "Montecarlo fetched", "TeamData Fetched", "Progress", "JIC", "Calendar fetched", "Contract fetched", "Staff Fetched", "Engines fetched", "Results fetched", "Year fetched", "Numbers fetched", "H2H fetched", "DriversH2H fetched", "H2HDriver fetched", "Retirement fetched", "Prediction Fetched", "Events to Predict Fetched", "Events to Predict Modal Fetched"] const glowSpot = document.querySelector('.glow-spot'); const blockDiv = document.getElementById('blockDiv'); let difficulty_dict = { "-2": "Custom", 0: "default", 1: "reduced weight", 2: "extra-hard", 3: "brutal", 4: "unfair", 5: "insane", 6: "impossible" } let inverted_difficulty_dict = { "disabled": -1, "default": 0, "reduced weight": 1, "extra-hard": 2, "brutal": 3, "unfair": 4, "insane": 5, "impossible": 6 } let difcultyCustom = "default" export let game_version = 2023; export let custom_team = false; export let nightlyBlock = false; export let seasonModData = {}; let latestSaveYear = null; let firstShow = false; let configCopy; let managingTeamChanged = false; let isSaveSelected = 0; let scriptSelected = 0; let divBlocking = 1; let saveName; let tempImageData = null; let lastVisibleIndex = 0; let viewerLoaded = false; export let selectedTheme = "default-theme"; let isNightlyHost = false; let hasPatreonThemeAccess = false; let newsAvailable = { "normal": false, "turning": false, } let versionNow; const versionPanel = document.querySelector('.version-panel'); const versionBadge = document.querySelector('.badge-version'); const parchModalTitle = document.getElementById("patchModalTitle") let notificationsQueue = []; let isShowingNotification = false; const repoOwner = 'IUrreta'; const repoName = 'DatabaseEditor'; (function () { const originalLog = console.log; const originalError = console.error; const logArray = []; console.log = function (...args) { logArray.push({ type: 'log', message: args, timestamp: new Date() }); originalLog.apply(console, args); }; console.error = function (...args) { logArray.push({ type: 'error', message: args, timestamp: new Date() }); originalError.apply(console, args); }; window.getLogEntries = () => logArray; })(); export function setSaveName(name) { saveName = name; } export function getSaveName() { return saveName; } export function setIsShowingNotification(value) { isShowingNotification = value; } /** * get the patch notes from the actual version fro the github api */ async function getPatchNotes() { try { if (versionNow.slice(-3) !== "dev" && !versionNow.includes("nightly")) { let response = await fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/tags/${versionNow}`); let data = await response.json(); let changes = data.body; let changesHTML = DOMPurify.sanitize(marked(changes)); patchNotesBody.innerHTML = changesHTML let h1Elements = patchNotesBody.querySelectorAll("h1"); h1Elements.forEach(function (h1Element) { let h4Element = document.createElement("h4"); h4Element.textContent = h1Element.textContent; h4Element.classList.add("bold-font") patchNotesBody.replaceChild(h4Element, h1Element); }); let h2Elements = patchNotesBody.querySelectorAll("h2"); h2Elements.forEach(function (h1Element) { let h4Element = document.createElement("h4"); h4Element.textContent = h1Element.textContent; h4Element.classList.add("bold-font") patchNotesBody.replaceChild(h4Element, h1Element); }); } else if (versionNow.includes("nightly")) { let response = await fetch('/data/nightly_patch_notes.md'); let changes = await response.text(); let changesHTML = DOMPurify.sanitize(marked(changes)); patchNotesBody.innerHTML = changesHTML let h1Elements = patchNotesBody.querySelectorAll("h1"); h1Elements.forEach(function (h1Element) { let h4Element = document.createElement("h4"); h4Element.textContent = h1Element.textContent; h4Element.classList.add("bold-font") patchNotesBody.replaceChild(h4Element, h1Element); }); let h2Elements = patchNotesBody.querySelectorAll("h2"); h2Elements.forEach(function (h1Element) { let h4Element = document.createElement("h4"); h4Element.textContent = h1Element.textContent; h4Element.classList.add("bold-font") patchNotesBody.replaceChild(h4Element, h1Element); }); } } catch { console.log("Couldn't find patch notes") } } // Patreon OAuth Logic if (patreonLoginButton) { patreonLoginButton.addEventListener('click', () => { window.location.href = '/api/auth/patreon/login'; }); } if (patreonToolLoginButton) { patreonToolLoginButton.addEventListener('click', () => { window.location.href = '/api/auth/patreon/login'; }); } if (patreonLogoutButton) { patreonLogoutButton.addEventListener('click', () => { handleLogout(); }); } if (userToolButton) { userToolButton.addEventListener('click', () => { const userToolMenu = document.querySelector('.userToolMenu'); if (userToolMenu) { userToolMenu.classList.toggle('hidden'); } }); } if (saveFileButton && saveFileInput) { saveFileInput.addEventListener('change', async (event) => { const file = event.target.files[0]; if (file) { await processSaveFile(file); } saveFileInput.value = ''; }); saveFileButton.addEventListener('click', async () => { const ok = await confirmModal({ title: "Warning about selecting your save file", body: "Selecting your save file this way (in stead of drag and drop) will not save your save in the Recents section. Are you sure you want to continue?", confirmText: "Continue", cancelText: "Cancel" }) if (ok) { saveFileInput.click(); } }); } async function handleLogout() { try { const response = await fetch('/api/auth/patreon/logout'); if (response.ok) { console.log("Logout successful"); updatePatreonUI({ isLoggedIn: false, tier: 'Free', tierNumber: 0, whitelisted: false, paidMember: false }); window.location.reload(); } } catch (error) { console.error("Logout failed", error); } } /** * Retrieves the user's Patreon tier from the cookie. * @returns {Promise<{paidMember: boolean, tier: string, tierNumber?: number, whitelisted: boolean, isLoggedIn: boolean, user: {fullName: string}}>} An object containing the user's tier information. */ export async function getUserTier() { try { const response = await fetch('/api/me'); const data = await response.json(); // The structure matches what api/me.js returns //set a window variable with the user data to be used in other places of the frontend without needing to call the api again let windowData = { paidMember: data.paidMember, tier: data.tier, tierNumber: data.tierNumber, whitelisted: !!data.whitelisted, isLoggedIn: data.isLoggedIn, }; window.__USER_DATA__ = windowData; windowData.user = { fullName: data.user?.fullName || '' }; return windowData; } catch (error) { console.error("Failed to check auth status", error); return { paidMember: false, tier: 'Free', whitelisted: false, isLoggedIn: false }; } } async function validateSession() { try { const res = await fetch("/api/check-cookie"); const data = await res.json(); // Only force an OAuth refresh when an existing cookie is detected but invalid/legacy. // Not having a cookie simply means "not logged in" and should not redirect. if (data.valid === false && data.hasCookie === true) { console.log("Old Patreon cookie → redirecting to login"); window.location.href = "/api/auth/patreon/login"; return false; } return true; } catch (err) { console.error("Error checking Patreon session:", err); return false; } } // Check for OAuth code const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { console.log("There is code") // Clear the code from URL to prevent re-submission on refresh window.history.replaceState({}, document.title, window.location.pathname); fetch(`/api/auth/patreon/verify?code=${code}`) .then(res => res.json()) .then(data => { if (data.success) { new_update_notifications(`Welcome ${data.user.fullName}! Tier: ${data.tier}`, "success"); // Update UI updatePatreonUI(data); maybeReloadForNightlyAccess(data); } else { new_update_notifications(`Login failed: ${data.error}`, "error"); updatePatreonUI(data); } }) .catch(err => { console.error('Patreon verification error:', err); new_update_notifications("Error verifying Patreon status", "error"); }); } else { validateSession().then(() => { getUserTier().then(updatePatreonUI); }); } function maybeReloadForNightlyAccess(tierInfo) { const isNightly = window.location.hostname.includes("nightly"); if (!isNightly) return; const insiderOrFounder = tierInfo?.tier === "Insider" || tierInfo?.tier === "Founder"; if (nightlyBlock && tierInfo?.isLoggedIn && insiderOrFounder) { setTimeout(() => window.location.reload(), 50); } } function updatePatreonUI(tier) { hasPatreonThemeAccess = !!tier.paidMember; init_colors_dict(selectedTheme) console.log("Updating Patreon UI with tier:", tier); if (tier.paidMember) { patreonUnlockables.classList.remove("d-none"); patreonThemes.classList.remove("d-none"); document.getElementById("patreonStatusText").textContent = tier.tier loadTheme(); } else { patreonUnlockables.classList.add("d-none"); patreonThemes.classList.add("d-none"); document.getElementById("patreonStatusText").textContent = tier.isLoggedIn ? tier.tier : "Not logged in" selectedTheme = "default-theme"; document.querySelector("body").className = "font default-theme"; init_colors_dict(selectedTheme); updateToolbarThemeLogo(); syncNightlyIndicator(); } syncNightlyThemeVisibility(); const hasCreateNewsAccess = tier?.tierNumber === 3 || tier?.tier === "Founder" || !!tier?.whitelisted; if (!hasCreateNewsAccess){ //remove the button from the DOM entirely document.querySelector("#createCustomNews")?.remove(); } if (tier.isLoggedIn) { document.querySelector(".user-name-and-logout-tool").classList.remove("d-none"); document.getElementById("userToolName").textContent = tier.user.fullName; patreonToolLoginButton.classList.add("d-none"); } else { document.querySelector(".user-name-and-logout-tool").classList.add("d-none"); patreonToolLoginButton.classList.remove("d-none"); } manageNewsStatus(tier); if (turningPointsFrequencyConfig) { const insiderOrFounder = tier?.tier === "Insider" || tier?.tier === "Founder"; if (tier?.isLoggedIn && insiderOrFounder) { turningPointsFrequencyConfig.classList.remove("d-none"); } else { turningPointsFrequencyConfig.classList.add("d-none"); } } } function editModeHandler() { if (isDraftProfileSelected()) { new_update_notifications("Draft creation is not implemented yet. For now, this button only generates editable random values.", "error"); return; } let stats = ""; document.querySelectorAll(".elegible").forEach(function (elem) { stats += elem.value + " "; }); stats = stats.slice(0, -1); let id; if (document.querySelector(".clicked").dataset.driverid) { id = document.querySelector(".clicked").dataset.driverid; } let driverName = getName(document.querySelector(".clicked .name-div-edit-stats")); driverName = make_name_prettier(driverName); document.querySelector(".clicked").dataset.stats = stats; let globalMentality = 2 let mentality = -1 if (document.querySelector(".clicked").dataset.mentality0) { mentality = "" document.querySelectorAll(".mentality-level-indicator").forEach(function (elem, index) { mentality += elem.dataset.value + " " document.querySelector(".clicked").dataset["mentality" + index] = elem.dataset.value globalMentality += parseInt(elem.dataset.value) }) mentality = mentality.slice(0, -1) globalMentality = Math.floor(globalMentality / 3) } document.querySelector(".clicked").dataset.globalMentality = globalMentality let new_ovr = calculateOverall(stats, typeOverall); document.querySelector(".clicked").childNodes[1].childNodes[0].textContent = new_ovr let retirement = document.querySelector(".actual-retirement").textContent let age = document.querySelector(".actual-age").textContent document.querySelector(".clicked").dataset.retirement = retirement; let ageGap = parseInt(document.querySelector(".clicked").dataset.age - age); document.querySelector(".clicked").dataset.age = age; let newName = document.querySelector("#driverStatsTitle textarea")?.value ?? document.querySelector("#driverStatsTitle").textContent; if (newName === document.querySelector(".clicked").dataset.name) { newName = "-1" } else { update_name(id, newName) } let newCode = document.querySelector("#driverCode textarea")?.value ?? document.querySelector("#driverCode").textContent; if (newCode === document.querySelector(".clicked").dataset.code) { newCode = "-1" } else { document.querySelector(".clicked").dataset.driverCode = newCode } let driverNum = document.querySelector(".number-holder").textContent; let wants1, superLicense, isRetired; document.querySelector(".clicked").dataset.number = driverNum; if (document.querySelector("#driverNumber1").checked) { wants1 = 1; document.querySelector(".clicked").dataset.numWC = 1; } else { wants1 = 0; document.querySelector(".clicked").dataset.numWC = 0; } if (document.querySelector("#retiredInput").checked) { isRetired = 1; document.querySelector(".clicked").dataset.isRetired = 1; } else { isRetired = 0; document.querySelector(".clicked").dataset.isRetired = 0; } document.querySelector(".clicked").dataset.numWC = wants1; if (document.getElementById("superLicense").checked) { superLicense = 1; document.querySelector(".clicked").dataset.superLicense = 1; } else { superLicense = 0; document.querySelector(".clicked").dataset.superLicense = 0; } let marketability = document.getElementById("marketabilityInput").value; let dataStats = { driverID: id, driver: driverName, statsArray: stats, typeStaff: typeEdit, retirement: retirement, age: ageGap, isRetired: isRetired, driverNum: driverNum, wants1: wants1, mentality: mentality, superLicense: superLicense, marketability: marketability, newName: newName, newCode: newCode, }; const command = new Command("editStats", dataStats); command.execute(); } function calendarModeHandler() { const raceArray = []; document.querySelectorAll(".race-calendar").forEach((race) => { let raceData = { trackId: race.dataset.trackid.toString(), rainPractice: race.dataset.rainP.toString(), rainQuali: race.dataset.rainQ.toString(), rainRace: race.dataset.rainR.toString(), type: race.dataset.type.toString(), state: race.dataset.state.toString(), isF2Race: race.dataset.isf2 ? Number(race.dataset.isf2) : 0, isF3Race: race.dataset.isf3 ? Number(race.dataset.isf3) : 0, }; raceArray.push(raceData); }); let dataCalendar = { racesData: raceArray }; const command = new Command("editCalendar", dataCalendar); command.execute(); } function regulationsModeHandler() { const data = gather_regulations_data(); if (!data) { new_update_notifications("Regulations not loaded", "error"); return; } const command = new Command("editRegulations", data); command.execute(); } function teamsModeHandler() { let seasonObjData = document.querySelector("#seasonObjectiveInput").value; let longTermData = longTermObj; let longTermYearData = document.querySelector("#longTermInput").value; let teamBudgetData = document.querySelector("#teamBudgetInput").value.replace(/[$,]/g, ""); let costCapTransactionData = originalCostCap - document.querySelector("#costCapInput").value.replace(/[$,]/g, ""); let confidenceData = document.querySelector("#confidenceInput").value; let facilitiesData = gather_team_data() let pitCrew = gather_pit_crew() let engine = document.querySelector("#engineButton").dataset.value let data = { teamID: teamCod, facilities: facilitiesData, seasonObj: seasonObjData, longTermObj: longTermData, longTermYear: longTermYearData, teamBudget: teamBudgetData, costCapEdit: costCapTransactionData, confidence: confidenceData, pitCrew: pitCrew, engine: engine, teamName: default_dict[teamCod], } const command = new Command("editTeam", data); command.execute(); } function performanceModeHandler() { let data; if (teamsEngine === "teams") { if (performanceDetailsMode === "expertise") { data = { teamID: teamSelected, expertise: gather_team_expertise_data(), teamName: document.querySelector(".selected").dataset.teamname } const command = new Command("editExpertise", data); command.execute(); return; } let parts = {}; let n_parts_designs = {}; let loadouts = {} document.querySelectorAll(".part-performance").forEach(function (elem) { let part = elem.dataset.part; let partID = elem.dataset.partid; let loadout1 = elem.dataset.loadout1; let loadout2 = elem.dataset.loadout2; let stats = {}; elem.querySelectorAll(".part-performance-stat").forEach(function (stat) { if (stat.dataset.attribute !== "-1") { let statNum = stat.dataset.attribute; let value = stat.querySelector("input").value.split(" ")[0]; stats[statNum] = value; } }); stats["designEditing"] = elem.querySelector(".part-subtitle").dataset.editing parts[part] = stats; loadouts[partID] = [loadout1, loadout2] }) document.querySelectorAll(".one-part").forEach(function (elem) { let designID = elem.querySelector(".one-part-name").dataset.designId let number = elem.querySelector(".n-parts").innerText.split("x")[1] n_parts_designs[designID] = number }) data = { teamID: teamSelected, parts: parts, n_parts_designs: n_parts_designs, loadouts: loadouts, teamName: document.querySelector(".selected").dataset.teamname } const command = new Command("editPerformance", data); command.execute(); } else if (teamsEngine === "engines") { const engineData = gather_engines_data() const officialEngines = {} for (let engineId in engineData) { if (Number(engineId) <= 10) { officialEngines[engineId] = engineData[engineId] } } if (Object.keys(officialEngines).length) { const command = new Command("editEngine", { engines: officialEngines }) command.execute() } const customEnginesData = gather_custom_engines_data() if (Object.keys(customEnginesData).length) { const command = new Command("customEngines", { enginesData: customEnginesData }) command.execute() } } } export function first_show_animation() { let button = document.querySelector(".save-button") if (!firstShow) { firstShow = true; button.classList.add("first-show") setTimeout(function () { button.classList.remove('first-show'); }, 3000); } } let saveButtonCustomHandler = null; export function manageSaveButton(show, mode, customHandler) { let button = document.querySelector(".save-button") button.removeEventListener("click", editModeHandler); button.removeEventListener("click", calendarModeHandler); button.removeEventListener("click", regulationsModeHandler); button.removeEventListener("click", teamsModeHandler); button.removeEventListener("click", performanceModeHandler); if (saveButtonCustomHandler) { button.removeEventListener("click", saveButtonCustomHandler); saveButtonCustomHandler = null; } if (!show) { button.classList.add("d-none") } else { button.classList.remove("d-none") first_show_animation() } if (mode === "stats") { button.addEventListener("click", editModeHandler); } else if (mode === "calendar") { button.addEventListener("click", calendarModeHandler); } else if (mode === "regulations") { button.addEventListener("click", regulationsModeHandler); } else if (mode === "teams") { button.addEventListener("click", teamsModeHandler); } else if (mode === "performance") { button.addEventListener("click", performanceModeHandler); } else if (mode === "custom" && typeof customHandler === "function") { saveButtonCustomHandler = customHandler; button.addEventListener("click", saveButtonCustomHandler); } } export async function updateFront(data) { console.log("UPDATING FRONT") console.log(data) let responseTyppe = data.responseMessage let message = data.content let handler = messageHandlers[responseTyppe]; if (handler) { handler(message); } if (data.noti_msg !== undefined) { new_update_notifications(data.noti_msg, "success"); } if (data.isEditCommand !== undefined) { checkOpenSlideUp() } if (data.unlocksDownload !== undefined) { downloadSaveButton.classList.remove("hidden") } } export function new_update_notifications(message, type = "success") { notificationsQueue.push(message); showNextNotification(type); } function showNextNotification(type) { if (isShowingNotification || notificationsQueue.length === 0) { return; } isShowingNotification = true; const nextMessage = notificationsQueue.shift(); const footerNotification = document.querySelector('.footer-notification'); footerNotification.innerHTML = nextMessage; if (type === "error") { footerNotification.classList.add('error'); } else { footerNotification.classList.remove('error'); } footerNotification.classList.add('show'); if (type !== "error") { setTimeout(() => { footerNotification.classList.remove('show'); isShowingNotification = false; //wait another 250ms setTimeout(() => { showNextNotification(); }, 550); }, 4000); } } export function make_name_prettier(text) { const words = text.trim().split(/\s+/); if (words.length < 2) { return ""; } const lastWord = words.pop(); return lastWord.charAt(0).toUpperCase() + lastWord.slice(1).toLowerCase(); } function orderTeamTemplatesByStandings(standingsRows) { const parent = document.querySelector(".main-columns-drag-section.teams-columns"); if (!parent) return; const templates = Array.from(parent.querySelectorAll(":scope > .team-template")); if (templates.length === 0) return; const positionByTeamId = new Map(); if (Array.isArray(standingsRows)) { standingsRows.forEach((row) => { if (!Array.isArray(row) || row.length < 2) return; const teamId = Number(row[0]); const position = Number(row[1]); if (position <= 0) return; const prev = positionByTeamId.get(teamId); if (prev === undefined || position < prev) { positionByTeamId.set(teamId, position); } }); } const decorated = templates.map((el, index) => { const staffSection = el.querySelector(".staff-section[data-teamid]"); const teamId = staffSection ? Number(staffSection.dataset.teamid) : NaN; const position = positionByTeamId.get(teamId); return { el, index, teamId, position }; }); decorated.sort((a, b) => { const aPos = a.position ?? 999; const bPos = b.position ?? 999; return aPos - bPos || a.index - b.index; }); const frag = document.createDocumentFragment(); decorated.forEach(({ el }) => frag.appendChild(el)); parent.appendChild(frag); } const messageHandlers = { "ERROR": (message) => { update_notifications(message[1], "error"); }, "Save loaded succesfully": (message) => { isSaveSelected = 1; viewerLoaded = false; remove_drivers(); removeStatsDrivers(); listenersStaffGroups(); place_drivers(message); sortList("free-drivers"); place_drivers_editStats(message); }, "Drivers fetched": (message) => { remove_drivers(); removeStatsDrivers(); listenersStaffGroups(); place_drivers(message); sortList("free-drivers"); place_drivers_editStats(message); initFreeDriversElems(); initStatsDrivers(); }, "Staff fetched": (message) => { remove_drivers(true); removeStatsDrivers(true); place_staff(message); sortList("free-staff") place_staff_editStats(message); initFreeDriversElems(); initStatsDrivers(); }, "Random staff draft fetched": (message) => { loadRandomStaffDraft(message); }, "Random draft forename fetched": (message) => { applyDraftForenameUpdate(message); }, "Draft country locale fetched": (message) => { applyDraftCountryLocale(message); }, "Calendar fetched": (message) => { load_calendar(message) }, "Regulations fetched": (message) => { load_regulations(message) }, "Engines fetched": (message) => { manage_engineStats(message[0]); update_engine_allocations(message); }, "Contract fetched": (message) => { manage_modal(message); }, "Junior team drivers fetched": (message) => { loadJuniorTeamDrivers(message); }, "Year fetched": (message) => { latestSaveYear = Number(message); generateYearsMenu(message); }, "Previous year teams standings fetched": (message) => { const standings = message?.standings || message; orderTeamTemplatesByStandings(standings); }, "Numbers fetched": (message) => { loadNumbers(message); }, "H2H fetched": (message) => { sprintsListeners(); racePaceListener(); qualiPaceListener() manage_h2h_bars(message); }, "DriversH2H fetched": (message) => { load_drivers_h2h(message); }, "H2HDriver fetched": (message) => { load_labels_initialize_graphs(message); }, "Results fetched": (message) => { new_drivers_table(message[0]); new_load_drivers_table(message.slice(1)); new_teams_table(message[0]); new_load_teams_table(message.slice(1)); }, "TeamData fetched": (message) => { fillLevels(message) }, "Events to Predict Fetched": (message) => { placeRaces(message.slice(1)) }, "Events to Predict Modal Fetched": (message) => { placeRacesInModal(message.slice(1)) }, "Prediction Fetched": (message) => { predictDrivers(message.slice(1)) }, "Montecarlo Fetched": (message) => { loadMontecarlo(message.slice(1)) }, "Progress": (message) => { manageProgress(message.slice(1)) }, "Config": (message) => { manage_config(message) document.querySelector("#transferpill").click(); }, "24 Year": (message) => { manage_config(message, true) }, "Performance fetched": (message) => { load_performance(message[0]) load_attributes(message[1]) //wait 100 ms setTimeout(function () { order_by("overall") }, 100) }, "Season performance fetched": (message) => { load_performance_graph(message) }, "Parts stats fetched": (message) => { load_parts_stats(message[0]) load_parts_list(message[1]) update_max_design(message[2]) if (message[3]) { load_team_expertise(message[3]) } }, "Game Year": (message) => { manage_game_year(message) }, "Part values fetched": (message) => { load_one_part(message) }, "Team expertise fetched": (message) => { load_team_expertise(message) }, "Cars fetched": (message) => { load_cars(message[0]) load_car_attributes(message[1]) order_by("overall") }, "Custom Engines fetched": (message) => { load_custom_engines(message.slice(1)) }, "Mod data fetched": (message) => { seasonModData = message || {}; updateEditsWithModData(message) syncAduoTpToggles(message?.aduo_tp_enabled); syncMods2025Dependencies(); syncMods2026Dependencies(); syncMods2026ApplyAllButtonState(); if (latestSaveYear) { generateYearsMenu(latestSaveYear); } }, "Mod compatibility": (message) => { updateMod2025Blocking(message) }, "Mod 2026 compatibility": (message) => { updateMod2026Blocking(message) resetStaffIDChanges(); }, "News fetched": (message) => { place_news(message, newsAvailable) updateNewsYearsButton(message) askFixDoublePointsBug(message) }, "News from season fetched": (message) => { place_news(message, newsAvailable) }, "Save selected finished": async (message) => { await migrateLegacyNewsOnce(); generateNews(); }, "Record fetched": (message) => { loadRecordsList(message) }, "Team record fetched": (message) => { loadTeamRecordsList(message) }, "Double points bug fixed": (message) => { //TODO CLICK ON THE FIRST EYAR OF yearMenu }, "Season review data fetched": (message) => { populateSeasonReview(message) }, "Session results fetched": (message) => { onSessionResultsFetched(message); } }; function removeLegacyKeys(base) { const lsNewsKey = `${base}_news`; const lsTPKey = `${base}_tps`; try { console.log("[migrate] Deleting legacy localStorage keys:", lsNewsKey, lsTPKey); localStorage.removeItem(lsNewsKey); localStorage.removeItem(lsTPKey); } catch (e) { console.warn("[migrate] Failed to remove legacy keys:", e); } } async function migrateLegacyNewsOnce() { const base = getSaveName().split('.')[0]; const lsFlagKey = `${base}_migration_v1_done`; const lsNewsKey = `${base}_news`; const lsTPKey = `${base}_tps`; // 1) Si ya está migrado, BORRAR SIEMPRE y salir if (localStorage.getItem(lsFlagKey) === "1") { removeLegacyKeys(base); return; } // 2) Leer posibles datos legacy const lsNewsTxt = localStorage.getItem(lsNewsKey); const lsTPTxt = localStorage.getItem(lsTPKey); // 3) Si no hay nada que migrar, marca flag y BORRA igual por si quedaron restos if (!lsNewsTxt && !lsTPTxt) { localStorage.setItem(lsFlagKey, "1"); removeLegacyKeys(base); return; } // 4) Hay algo que migrar → pide al worker try { const resp = await new Command("migrateFromLocalStorage", { base, lsNewsTxt, // pueden ser null; el worker ya valida lsTPTxt }).promiseExecute(); // Considera como éxito "Migration done" o "Already migrated" por si reintentas if (resp?.responseMessage === "Migration done" || resp?.responseMessage === "Already migrated") { localStorage.setItem(lsFlagKey, "1"); removeLegacyKeys(base); // BORRA tras éxito } else { console.warn("[migrate] Unexpected response:", resp); // Si quieres ser agresivo igualmente: localStorage.setItem(lsFlagKey, "1"); removeLegacyKeys(base); } } catch (e) { console.error("[migrate] Migration error (front):", e); // No marcamos flag en error para poder reintentar después. // Pero si quieres limpiar sí o sí, podrías optar por: // removeLegacyKeys(base); } } if (glowSpot && blockDiv) { const defaultPosition = { left: '50%', top: '0', transform: 'translateX(-50%)', }; let restoreTimeout; const isLandingVisible = () => !blockDiv.classList.contains('disappear'); const rootStyle = document.documentElement.style; const setGlowVars = (x, y) => { rootStyle.setProperty('--glow-x', x); rootStyle.setProperty('--glow-y', y); }; const syncGlowVarsToGlowSpotCenter = () => { const rect = glowSpot.getBoundingClientRect(); setGlowVars(`${rect.left + rect.width / 2}px`, `${rect.top + rect.height / 2}px`); }; const resetGlowSpotPosition = () => { glowSpot.style.left = defaultPosition.left; glowSpot.style.top = defaultPosition.top; glowSpot.style.transform = defaultPosition.transform; syncGlowVarsToGlowSpotCenter(); }; const updateGlowSpotPosition = (event) => { if (!isLandingVisible()) { return; } glowSpot.classList.remove('glow-spot--off'); const x = `${event.clientX}px`; const y = `${event.clientY}px`; glowSpot.style.left = x; glowSpot.style.top = y; glowSpot.style.transform = 'translate(-50%, -50%)'; setGlowVars(x, y); }; const fadeToDefaultPosition = () => { glowSpot.classList.add('glow-spot--off'); clearTimeout(restoreTimeout); restoreTimeout = setTimeout(() => { resetGlowSpotPosition(); glowSpot.classList.remove('glow-spot--off'); }, 200); }; const observer = new MutationObserver(() => { if (isLandingVisible()) { glowSpot.classList.remove('glow-spot--off'); } else { fadeToDefaultPosition(); } }); observer.observe(blockDiv, { attributes: true, attributeFilter: ['class'] }); resetGlowSpotPosition(); window.addEventListener('mousemove', updateGlowSpotPosition); } export async function generateNews() { const patreonTier = await getUserTier(); checkGenerableNews(patreonTier); // lanzar sin payload, el worker lee de DB new Command("generateNews", {}).execute(); // loader UI (igual que antes si quieres) const newsView = document.getElementById("news"); const loaderDiv = document.createElement('div'); loaderDiv.classList.add('loader-div', 'general-news-loader'); const loadingSpan = document.createElement('span'); loadingSpan.textContent = "Updating news"; const loadingDots = document.createElement('span'); loadingDots.textContent = "."; loadingDots.classList.add('loading-dots'); loadingSpan.textContent = "Updating news"; loadingSpan.appendChild(loadingDots); setInterval(() => { if (loadingDots.textContent.length >= 3) loadingDots.textContent = "."; else loadingDots.textContent += "."; }, 500); const progressBar = document.createElement('div'); progressBar.classList.add('ai-progress-bar'); const progressDiv = document.createElement('div'); progressDiv.classList.add('progress-div', 'general-news-progress-div'); loaderDiv.appendChild(loadingSpan); progressBar.appendChild(progressDiv); loaderDiv.appendChild(progressBar); startGeneralNewsProgress(progressDiv); newsView.appendChild(loaderDiv); } export function startGeneralNewsProgress(progressDiv) { let width = 0; const id = setInterval(() => { if (!progressDiv?.isConnected) { clearInterval(id); return; } if (width >= 100) { clearInterval(id); return; } width++; progressDiv.style.width = width + '%'; }, 150); progressDiv._progressIntervalId = id; } function update_engine_allocations(message) { let engine_map = {} message[1].forEach(function (team) { engine_map[team[0]] = team[1] }) setEngineAllocations(engine_map) window.__ENGINE_ALLOCATIONS__ = engine_map for (let key in engine_names) { if (key > 10) { deleteEngineName(key) } } message[0].forEach(function (engine) { const engineId = Number(engine?.[0]); if (engineId > 10 || engineId === 10) { addEngineName(engineId, engine[2]) } }) window.__ENGINE_NAMES__ = { ...engine_names } reloadTables() } function resizeWindowToHeight(mode) { if (mode === "11teams") { document.querySelectorAll(".main-resizable").forEach(function (elem) { elem.style.height = "720.5px" if (elem.id === "enginesPerformance") { elem.style.maxHeight = "720px" } }) document.querySelectorAll(".staff-list").forEach(function (elem) { elem.style.height = "672px" }) document.querySelectorAll(".parts-list").forEach(function (elem) { elem.classList.remove("noCustom") }) document.getElementById("free-drivers").style.height = "672px" document.getElementById("free-staff").style.height = "672px" } else if (mode === "10teams") { document.querySelectorAll(".main-resizable").forEach(function (elem) { elem.style.height = "660px" if (elem.id === "enginesPerformance") { elem.style.maxHeight = "660px" } }) document.querySelectorAll(".parts-list").forEach(function (elem) { elem.classList.add("noCustom") }) document.querySelectorAll(".staff-list").forEach(function (elem) { elem.style.height = "612px" }) document.getElementById("free-drivers").style.height = "612px" document.getElementById("free-staff").style.height = "612px" } } function manage_game_year(info) { let year = info[0] if (year === "24") { document.getElementById("year23").classList.remove("activated") document.getElementById("year24").classList.add("activated") document.getElementById("drs24").classList.remove("d-none") document.getElementById("drs24").dataset.attribute = "3" game_version = 2024 setMaxRaces(24) manage_custom_team(info) document.querySelectorAll(".brake-cooling-replace").forEach(function (elem) { elem.textContent = "Tyre preservation" }) document.querySelectorAll(".engine24").forEach(function (elem) { elem.classList.add("d-none") }) document.querySelector(".only-mentality").classList.remove("d-none") } else if (year === "23") { resizeWindowToHeight("10teams") document.getElementById("year24").classList.remove("activated") document.getElementById("year23").classList.add("activated") document.getElementById("drs24").classList.add("d-none") document.getElementById("drs24").dataset.attribute = "-1" if (32 in combined_dict) { delete combined_dict[32] } game_version = 2023 setMidGrid(10) setMaxRaces(23) setRelativeGrid(5) manage_custom_team([null, null]) document.querySelectorAll(".brake-cooling-replace").forEach(function (elem) { elem.textContent = "Brake cooling" }) document.querySelectorAll(".engine24").forEach(function (elem) { elem.classList.remove("d-none") }) document.querySelector(".only-mentality").classList.add("d-none") } replace_modal_teams(game_version) } function manage_custom_team(nameColor) { if (nameColor[1] !== null) { resizeWindowToHeight("11teams") custom_team = true combined_dict[32] = nameColor[1] abreviations_dict[32] = nameColor[1].slice(0, 3).toUpperCase() document.querySelectorAll(".ct-teamname").forEach(function (elem) { elem.dataset.teamshow = nameColor[1] }) const command = new Command("updateCombinedDict", { teamID: 32, newName: nameColor[1] }); command.execute(); document.querySelector(".lineup-team--cadillac").classList.remove("d-none") document.getElementById("customTeamTransfers").classList.remove("d-none") document.getElementById("customTeamPerformance").classList.remove("d-none") document.getElementById("customTeamDropdown").classList.remove("d-none") document.getElementById("customTeamComparison").classList.remove("d-none") document.getElementById("customTeamContract").classList.remove("d-none") document.getElementById("customizeTeam").classList.remove("d-none") document.querySelectorAll(".ct-replace").forEach(function (elem) { elem.textContent = nameColor[1].toUpperCase() }) document.querySelectorAll(".custom-car-performance").forEach(function (elem) { elem.classList.remove("d-none") }) replace_custom_team_color(nameColor[2], nameColor[3]) setMidGrid(11) setRelativeGrid(4.54) } else { resizeWindowToHeight("10teams") custom_team = false document.querySelector(".lineup-team--cadillac").classList.add("d-none") document.getElementById("customTeamTransfers").classList.add("d-none") document.getElementById("customTeamPerformance").classList.add("d-none") document.getElementById("customTeamDropdown").classList.add("d-none") document.getElementById("customTeamComparison").classList.add("d-none") document.getElementById("customTeamContract").classList.add("d-none") document.getElementById("customizeTeam").classList.add("d-none") document.querySelectorAll(".custom-car-performance").forEach(function (elem) { elem.classList.add("d-none") }) setMidGrid(10) setRelativeGrid(5) if (32 in combined_dict) { delete combined_dict[32] } } } function replace_custom_team_color(primary, secondary) { let root = document.documentElement; root.style.setProperty('--custom-team-primary', primary); root.style.setProperty('--custom-team-secondary', secondary); root.style.setProperty('--custom-team-primary-transparent', primary + "30"); root.style.setProperty('--custom-team-secondary-transparent', secondary + "30"); edit_colors_dict("320", primary) edit_colors_dict("321", secondary) document.getElementById("primarySelector").value = primary document.getElementById("secondarySelector").value = secondary document.getElementById("primaryReader").value = primary.toUpperCase() document.getElementById("secondaryReader").value = secondary.toUpperCase() } selectImageButton.addEventListener('click', () => { fileInput.click(); }); // Función para manejar la selección de archivo fileInput.addEventListener('change', (event) => { let file = event.target.files[0]; const reader = new FileReader(); reader.onload = function () { tempImageData = reader.result; document.querySelector(".logo-preview").src = reader.result; }; reader.readAsDataURL(file); }); export function replace_custom_team_logo(path) { //if not image selected, return if (!path) { return; } // Si el string base64 no tiene el prefijo, se lo agregamos. if (!path.startsWith("data:image/")) { // Ajusta el tipo de imagen ("png", "jpeg", etc.) según corresponda. path = "data:image/png;base64," + path; } logos_disc[32] = path; document.querySelectorAll(".custom-replace").forEach(function (elem) { elem.src = path; }); document.querySelector(".logo-preview").src = path; } document.querySelector(".gear-container").addEventListener("click", function () { let configDetailModal = new bootstrap.Modal(document.getElementById('configDetailModal'), { keyboard: false }) configDetailModal.show() }) function manage_config(info, year_config = false) { document.querySelector(".bi-gear-fill#settingsIcon").classList.remove("hidden") configCopy = info manage_config_content(info, year_config) } function replace_all_teams(info) { let teams = info["teams"] const alphatauri = teams["alphatauri"] const alpine = teams["alpine"] const alfa = teams["alfa"] const redbull = teams["redbull"] || "redbull" const aston = teams["aston"] || "aston" const williams = teams["williams"] || "williams" const haas = teams["haas"] || "haas" alphaTauriReplace(alphatauri) alpineReplace(alpine) williamsReplace(williams) haasReplace(haas) alfaReplace(alfa) redbullReplace(redbull) astonReplace(aston) update_logo("alpine", logos_configs[alpine], alpine) update_logo("williams", logos_configs[williams], williams) update_logo("haas", logos_configs[haas], haas) update_logo("alfa", logos_configs[alfa], alfa) update_logo("alphatauri", logos_configs[alphatauri], alphatauri) update_logo("redbull", logos_configs[redbull], redbull) update_logo("aston", logos_configs[aston], aston) // Notify other screens (e.g. transfers lineups circle) that team names/logos changed. document.dispatchEvent(new CustomEvent("teamsReplaced", { detail: { teams } })); } function manage_config_content(info, year_config = false) { if (info["renaultEngine"] === "honda") { setRenaultEnginePresentation("honda"); } else { setRenaultEnginePresentation("renault"); } replace_all_teams(info) if (!year_config) { let image = localStorage.getItem(`${saveName}_image`); if (image) { replace_custom_team_logo(image); } if (info["primaryColor"]) { replace_custom_team_color(info["primaryColor"], info["secondaryColor"]) } if (info["frozenMentality"] === 1) { document.getElementById("freezeMentalityToggle").checked = true } else { document.getElementById("freezeMentalityToggle").checked = false } if (info["refurbish"] === 1) { document.getElementById("refurbishingToggle").checked = true } else { document.getElementById("refurbishingToggle").checked = false } if (info["freezeDevelopment"] === 1) { document.getElementById("freezeDevelopmentToggle").checked = true } else { document.getElementById("freezeDevelopmentToggle").checked = false } document.querySelector(`.team-logo-container[data-teamid="${info["playerTeam"]}"]`).classList.add("active") update_difficulty_info(info["triggerList"]) update_mentality_span(info["frozenMentality"]) update_refurbish_span(info["refurbish"]) update_development_span(info["freezeDevelopment"]) if (forceEditorMinimapColorsToggle) { forceEditorMinimapColorsToggle.checked = parseInt(info["forceEditorMinimapColors"] || 0, 10) === 1; } if (turningPointsFrequencySlider) { let presetIndex = info?.turningPointsFrequencyPreset; if (presetIndex === undefined || presetIndex === null) { presetIndex = defaultTurningPointsFrequencyPreset; } turningPointsFrequencySlider.value = String(presetIndex); updateTurningPointsFrequencyUI(); } } } export function setRenaultEnginePresentation(engineMode) { const engineMenuItem = document.querySelector(".renault-engine-menu-item"); const engineTitleText = document.querySelector(".renault-engine-title-text"); const renaultLogo = document.querySelector(".renault-engine-logo"); if (engineMode === "honda") { if (engineMenuItem) { engineMenuItem.textContent = "Honda"; } if (engineTitleText) { engineTitleText.textContent = "HONDA"; } if (renaultLogo) { renaultLogo.src = "../assets/images/logos/honda.png"; renaultLogo.alt = "Honda logo"; } } else { if (engineMenuItem) { engineMenuItem.textContent = "Renault"; } if (engineTitleText) { engineTitleText.textContent = "RENAULT"; } if (renaultLogo) { renaultLogo.src = "../assets/images/logos/renault.png"; renaultLogo.alt = "Renault logo"; } } const renaultEngineTitle = document.querySelector(".renault-engine-title"); if (renaultEngineTitle) { renaultEngineTitle.classList.remove("engine-re", "engine-ho"); renaultEngineTitle.classList.add(engineMode === "honda" ? "engine-ho" : "engine-re"); } if (renaultLogo) { renaultLogo.style.display = "inline-block"; } } export function updateJenzerToDams(mode = "dams") { if (mode === "dams") { logos_disc[30] = '../assets/images/logos/dams.png' combined_dict[30] = "DAMS (F3)" } else if (mode === "jenzer") { logos_disc[30] = '../assets/images/logos/jenzer.png' combined_dict[30] = "Jenzer Motorsport (F3)" } } document.querySelectorAll(".color-picker").forEach(function (elem) { let reader = elem.parentNode.querySelector(".color-reader") elem.addEventListener("input", function () { reader.value = elem.value.toUpperCase() }) reader.value = elem.value.toUpperCase(); }) document.querySelectorAll(".color-reader").forEach(function (elem) { elem.addEventListener("input", function () { let picker = elem.parentNode.querySelector(".color-picker") picker.value = elem.value.toLowerCase() }) }) function update_difficulty_info(triggerList) { console.log("TRIGGER LIST", triggerList) //iterate through the objetc for (let key in triggerList) { let value = triggerList[key]; let nameSpan = document.getElementById(key) if (!nameSpan) continue; let status = nameSpan.parentNode.querySelector(".dif-status") let options; if (key === "lightDif"){ options = weightDifConfig } else{ options = defaultDifficultiesConfig } if (value < 0) { value = 0; } status.dataset.value = value; console.log("UPDATING DIFFICULTY", key, value, options[value]) status.textContent = options[value].text; status.className = `dif-status ${options[value].className}`; } } function change_css_variables(oldVar, newVar) { let root = document.documentElement; let newVal = getComputedStyle(root).getPropertyValue(newVar).trim(); root.style.setProperty(oldVar, newVal); } const { alphaTauriReplace, alpineReplace, williamsReplace, haasReplace, alfaReplace, redbullReplace, astonReplace } = createTeamReplacers({ combined_dict, abreviations_dict, edit_colors_dict, change_css_variables }); function replace_modal_teams(version) { if (version === 2024) { document.getElementById("alphaModalLogo").src = logos_configs["visarb"] document.getElementById("alphaModalLogo").className = "visarblogo non-changable" document.getElementById("alphaModalName").textContent = pretty_names["visarb"] document.getElementById("alfaModalLogo").src = logos_configs["stake"] document.getElementById("alfaModalName").textContent = pretty_names["stake"] } else if (version === 2023) { document.getElementById("alphaModalLogo").src = logos_configs["alphatauri"] document.getElementById("alphaModalLogo").className = "alphataurilogo non-changable" document.getElementById("alphaModalName").textContent = pretty_names["alphatauri"] document.getElementById("alfaModalLogo").src = logos_configs["alfa"] document.getElementById("alfaModalName").textContent = pretty_names["alfa"] } } //select all team-change-button document.querySelectorAll(".team-change-button").forEach(function (elem) { elem.querySelectorAll("a").forEach(function (a) { a.addEventListener("click", function () { elem.querySelector("button span").textContent = a.textContent elem.querySelector("button").dataset.value = a.dataset.value }) }) }) export function applyConfigFromEditorUI(overrides = {}) { let alphatauri = document.querySelector("#alphaTauriReplaceButton").querySelector("button").dataset.value let alpine = document.querySelector("#alpineReplaceButton").querySelector("button").dataset.value let williams = document.querySelector("#williamsReplaceButton").querySelector("button").dataset.value let haas = document.querySelector("#haasReplaceButton").querySelector("button").dataset.value let alfa = document.querySelector("#alfaReplaceButton").querySelector("button").dataset.value let redbull = document.querySelector("#redbullReplaceButton").querySelector("button").dataset.value let aston = document.querySelector("#astonReplaceButton").querySelector("button").dataset.value if (overrides && typeof overrides === "object") { if (typeof overrides.alphatauri === "string") alphatauri = overrides.alphatauri; if (typeof overrides.alpine === "string") alpine = overrides.alpine; if (typeof overrides.williams === "string") williams = overrides.williams; if (typeof overrides.haas === "string") haas = overrides.haas; if (typeof overrides.alfa === "string") alfa = overrides.alfa; if (typeof overrides.redbull === "string") redbull = overrides.redbull; if (typeof overrides.aston === "string") aston = overrides.aston; } let mentalityFrozen = 0; if (document.getElementById("freezeMentalityToggle").checked) { mentalityFrozen = 1; } let refurbish = 0; if (document.getElementById("refurbishingToggle").checked) { refurbish = 1; } let freezeDevelopment = 0; if (document.getElementById("freezeDevelopmentToggle").checked) { freezeDevelopment = 1; } let forceEditorMinimapColors = 0; if (forceEditorMinimapColorsToggle && forceEditorMinimapColorsToggle.checked) { forceEditorMinimapColors = 1; } let difficulty = 0; let disabledList = {} let triggerList = {} let playerTeam = managingTeamChanged ? document.querySelector(".team-logo-container.active").dataset.teamid : -1 document.querySelectorAll(".dif-status").forEach(function (elem) { let id = elem.parentNode.querySelector(".dif-name").id triggerList[id] = elem.dataset.value }) let data = { alphatauri: alphatauri, alpine: alpine, williams: williams, haas: haas, alfa: alfa, redbull: redbull, aston: aston, frozenMentality: mentalityFrozen, refurbish: refurbish, freezeDevelopment: freezeDevelopment, forceEditorMinimapColors: forceEditorMinimapColors, disabled: disabledList, triggerList: triggerList, playerTeam: playerTeam } const tpPresetIndex = parseInt(turningPointsFrequencySlider.value, 10); data.turningPointsFrequencyPreset = tpPresetIndex; changeTheme() if (custom_team) { data["primaryColor"] = document.getElementById("primarySelector").value data["secondaryColor"] = document.getElementById("secondarySelector").value replace_custom_team_color(data["primaryColor"], data["secondaryColor"]) } reload_performance_graph() reload_h2h_graphs() if (isSaveSelected === 1) { const command = new Command("configUpdate", data); command.execute(); let info = { teams: { alphatauri: alphatauri, alpine: alpine, williams: williams, haas: haas, alfa: alfa, redbull: redbull, aston: aston } } replace_all_teams(info) reloadTables() reload_performance_graph() reload_h2h_graphs() if (tempImageData) { localStorage.setItem(`${saveName}_image`, tempImageData); } replace_custom_team_logo(document.querySelector(".logo-preview").src) if (!configCopy || typeof configCopy !== "object") { configCopy = {}; } if (!configCopy.teams || typeof configCopy.teams !== "object") { configCopy.teams = {}; } configCopy.teams.alphatauri = alphatauri; configCopy.teams.alpine = alpine; configCopy.teams.williams = williams; configCopy.teams.haas = haas; configCopy.teams.alfa = alfa; configCopy.teams.redbull = redbull; configCopy.teams.aston = aston; configCopy.frozenMentality = mentalityFrozen; configCopy.refurbish = refurbish; configCopy.freezeDevelopment = freezeDevelopment; configCopy.forceEditorMinimapColors = forceEditorMinimapColors; configCopy.triggerList = triggerList; configCopy.turningPointsFrequencyPreset = tpPresetIndex; if (data.primaryColor) { configCopy.primaryColor = data.primaryColor; configCopy.secondaryColor = data.secondaryColor; } if (playerTeam !== -1) { configCopy.playerTeam = Number(playerTeam); } } } document.querySelector("#configDetailsButton").addEventListener("click", function () { applyConfigFromEditorUI(); }) async function askFixDoublePointsBug(message){ const bugInfo = message.doublePointsBug; if (!bugInfo.result) return; if (localStorage.getItem(`${saveName}_doublePointsBugIgnored_${bugInfo.raceId}`) === 'true') { return; } const ok = await confirmModal({ title: 'Fix Double Points Bug', body: 'The current save has a known issue with double points being awarded in certain races where a double DSQ Turning point happened. Do you want to fix this issue now?', confirmText: 'Yes, fix it', cancelText: 'No, ignore', }) if (ok) { const command = new Command("fixDoublePointsBug", { raceId: bugInfo.raceId }); command.execute(); } else{ //save in lcoalstorage a flag that he didn't want to fix the bug with raceid bugInfo.raceId localStorage.setItem(`${saveName}_doublePointsBugIgnored_${bugInfo.raceId}`, 'true'); } } let isDownloadingSave = false; let downloadSaveProgressStartedAt = 0; let downloadSaveProgressValue = 0; let downloadSaveProgressIntervalId = null; let downloadSaveProgressTimeoutId = null; let downloadSaveWorkerHandler = null; function setDownloadSaveProgress(percent) { if (!downloadSaveProgressFill) return; const clamped = Math.max(0, Math.min(100, percent)); downloadSaveProgressFill.style.width = `${clamped}%`; } function clearDownloadSaveWorkerHandler() { if (!downloadSaveWorkerHandler) return; dbWorker.removeEventListener("message", downloadSaveWorkerHandler); downloadSaveWorkerHandler = null; } function clearDownloadSaveProgressTimers() { if (downloadSaveProgressIntervalId !== null) { window.clearInterval(downloadSaveProgressIntervalId); downloadSaveProgressIntervalId = null; } if (downloadSaveProgressTimeoutId !== null) { window.clearTimeout(downloadSaveProgressTimeoutId); downloadSaveProgressTimeoutId = null; } } function resetDownloadSaveProgress() { clearDownloadSaveProgressTimers(); clearDownloadSaveWorkerHandler(); isDownloadingSave = false; downloadSaveProgressValue = 0; setDownloadSaveProgress(0); if (downloadSaveProgress) downloadSaveProgress.classList.add("hidden"); } function startDownloadSaveProgressSimulation() { if (!downloadSaveProgress || !downloadSaveProgressFill) return; clearDownloadSaveProgressTimers(); isDownloadingSave = true; downloadSaveProgressStartedAt = performance.now(); downloadSaveProgressValue = 0; setDownloadSaveProgress(0); downloadSaveProgress.classList.remove("hidden"); downloadSaveProgressIntervalId = window.setInterval(() => { if (!isDownloadingSave) return; const elapsed = performance.now() - downloadSaveProgressStartedAt; const fastPhase = Math.min(elapsed / 500, 1) * 45; // 0 -> 45 quickly const slowPhase = Math.min(Math.max(elapsed - 500, 0) / 4500, 1) * 50; // 45 -> 95 slower const target = Math.min(95, fastPhase + slowPhase); if (target > downloadSaveProgressValue) { downloadSaveProgressValue = target; setDownloadSaveProgress(downloadSaveProgressValue); } }, 100); downloadSaveProgressTimeoutId = window.setTimeout(() => { if (!isDownloadingSave) return; resetDownloadSaveProgress(); new_update_notifications("Save export timed out.", "error"); }, 20000); } function finishDownloadSaveProgress() { if (!downloadSaveProgress || !downloadSaveProgressFill) return; const elapsed = performance.now() - downloadSaveProgressStartedAt; const minVisibleMs = 700; const hideDelayMs = Math.max(250, minVisibleMs - elapsed); clearDownloadSaveProgressTimers(); clearDownloadSaveWorkerHandler(); isDownloadingSave = false; setDownloadSaveProgress(100); window.setTimeout(() => resetDownloadSaveProgress(), hideDelayMs); } const panicDownloadButton = document.getElementById("panicDownloadButton"); const downloadSaveIcon = document.querySelector(".bi-file-earmark-arrow-down"); function downloadExportedSave(command) { if (isDownloadingSave) return; startDownloadSaveProgressSimulation(); downloadSaveWorkerHandler = (msg) => { if (!isDownloadingSave) return; const response = msg?.data; if (!response) return; if (response.error) { console.error("Error exporting save:", response.error); resetDownloadSaveProgress(); new_update_notifications("Error exporting save.", "error"); return; } if (response.responseMessage !== "Database exported") return; try { const finalData = response?.content?.finalData; const metadata = response?.content?.metadata; const filename = metadata?.filename || saveName || "save.sav"; if (finalData == null) { throw new Error("Missing exported data"); } saveAs(new Blob([finalData], { type: "application/binary" }), filename); finishDownloadSaveProgress(); } catch (e) { console.error("Failed to download exported save:", e); resetDownloadSaveProgress(); new_update_notifications("Error exporting save.", "error"); } }; dbWorker.addEventListener("message", downloadSaveWorkerHandler); dbWorker.postMessage({ command, data: {} }); } if (downloadSaveIcon) { downloadSaveIcon.addEventListener("click", function () { downloadExportedSave("exportSave"); }) } if (panicDownloadButton) { panicDownloadButton.addEventListener("click", function () { downloadExportedSave("panicDownload"); }) } /** * checks if a save and a script have been selected to unlock the tool */ function check_selected() { if (isSaveSelected == 1 && scriptSelected == 1 && divBlocking == 1) { document.getElementById("blockDiv").classList.add("disappear") divBlocking = 0; } } h2hPill.addEventListener("click", function () { manageScripts("hide", "show", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(false) }) viewPill.addEventListener("click", function () { if (!viewerLoaded) { viewerLoaded = true document.getElementById("reviewpill").click(); } manageScripts("hide", "hide", "show", "hide", "hide", "hide", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(false) }) driverTransferPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "show", "hide", "hide", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(false) }) editStatsPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "show", "hide", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(true, "stats") }) constructorsPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "show", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(true, "teams") }) CalendarPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "hide", "show", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(true, "calendar") }) regulationsPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "hide", "hide", "show", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(true, "regulations") }) carPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "show", "hide") scriptSelected = 1 check_selected() manageSaveButton(!viewingGraph, "performance") }) modPill.addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "show") scriptSelected = 1 check_selected() manageSaveButton(false) }) newsPill.addEventListener("click", function () { manageScripts("show", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide") scriptSelected = 1 check_selected() manageSaveButton(false) }) document.querySelector(".toolbar-logo-and-title").addEventListener("click", function () { manageScripts("hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide", "hide") scriptSelected = 0 document.getElementById("blockDiv").classList.remove("disappear") if (document.querySelector(".scriptPills.active")) { document.querySelector(".scriptPills.active").classList.remove("active") } divBlocking = 1; check_selected() }) gamePill.addEventListener("click", function () { document.querySelector("#editorChanges").classList.add("d-none") document.querySelector("#gameChanges").classList.remove("d-none") document.querySelector("#patreonChanges").classList.add("d-none") }) editorPill.addEventListener("click", function () { document.querySelector("#editorChanges").classList.remove("d-none") document.querySelector("#gameChanges").classList.add("d-none") document.querySelector("#patreonChanges").classList.add("d-none") }) patreonPill.addEventListener("click", function () { document.querySelector("#patreonChanges").classList.remove("d-none") document.querySelector("#editorChanges").classList.add("d-none") document.querySelector("#gameChanges").classList.add("d-none") }) if (turningPointsFrequencySlider) { updateTurningPointsFrequencyUI(); turningPointsFrequencySlider.addEventListener("input", updateTurningPointsFrequencyUI); } document.getElementById("freezeMentalityToggle").addEventListener("change", function () { let value = this.checked; update_mentality_span(value) }); function update_mentality_span(value) { let span = document.querySelector("#mentalitySpan") if (value) { span.className = "option-state frozen" span.textContent = "Frozen" } else { span.className = "option-state default" span.textContent = "Unfrozen" } } document.getElementById("refurbishingToggle").addEventListener("change", function () { let value = this.checked; update_refurbish_span(value) }); function update_refurbish_span(value) { let span = document.querySelector("#refurbishSpan") if (value) { span.className = "option-state fixed" span.textContent = "Fixed" } else { span.className = "option-state default" span.textContent = "Default" } } document.getElementById("freezeDevelopmentToggle").addEventListener("change", function () { let value = this.checked; update_development_span(value) }); function update_development_span(value) { let span = document.querySelector("#developmentSpan") if (value) { span.className = "option-state frozen" span.textContent = "Frozen" } else { span.className = "option-state default" span.textContent = "Active" } } function manage_difficulty_warnings(level, triggerList) { const elements = [ "defaultDif", "lightDif", "researchDif", "statDif", "designTimeDif", "buildDif" ]; const selectedConfig = difficultyConfig[level] || difficultyConfig["default"]; elements.forEach(id => { document.getElementById(id).classList.add("d-none"); }); selectedConfig.visible.forEach(id => { document.getElementById(id).classList.remove("d-none"); }); elements.forEach(id => { if (selectedConfig[id] && triggerList[id] !== -1) { const elementConfig = selectedConfig[id]; const element = document.getElementById(id); element.className = elementConfig.className; element.textContent = elementConfig.text; } else if (triggerList[id] === -1) { document.getElementById(id).classList.add("disabled") } }); } function load_difficulty_warnings(triggerList) { for (let id in triggerList) { let warn = document.getElementById(id) let difName = difficulty_dict[triggerList[id]] if (triggerList[id] !== -1) { warn.className = difficultyConfig[difName][id].className warn.textContent = difficultyConfig[difName][id].text } else { warn.classList.add("disabled") } } } document.querySelectorAll(".one-difficulty .bi-plus").forEach(function (elem) { const title = elem.parentNode.parentNode const status = title.querySelector(".dif-status") const name = title.querySelector(".dif-name") let options; if (name.id === "lightDif") { options = weightDifConfig } else{ options = defaultDifficultiesConfig } const optionKeys = Object.keys(options).map(Number).sort((a, b) => a - b); elem.addEventListener("click", function () { const actualValue = Number(status.dataset.value); const actualIndex = optionKeys.indexOf(actualValue); const safeIndex = actualIndex === -1 ? 0 : actualIndex; const newKeyIndex = (safeIndex + 1) % optionKeys.length; const newValue = optionKeys[newKeyIndex]; status.dataset.value = newValue; status.textContent = options[newValue].text; status.className = `dif-status ${options[newValue].className}`; }); }); document.querySelectorAll(".one-difficulty .bi-dash").forEach(function (elem) { const title = elem.parentNode.parentNode const status = title.querySelector(".dif-status") const name = title.querySelector(".dif-name") let options; if (name.id === "lightDif"){ options = weightDifConfig } else{ options = defaultDifficultiesConfig } const optionKeys = Object.keys(options).map(Number).sort((a, b) => a - b); elem.addEventListener("click", function () { const actualValue = Number(status.dataset.value); const actualIndex = optionKeys.indexOf(actualValue); const safeIndex = actualIndex === -1 ? 0 : actualIndex; const newKeyIndex = (safeIndex - 1 + optionKeys.length) % optionKeys.length; const newValue = optionKeys[newKeyIndex]; status.dataset.value = newValue; status.textContent = options[newValue].text; status.className = `dif-status ${options[newValue].className}`; }); }); /** * Manages the stats of the divs associated with the pills * @param {Array} divs array of state of the divs */ function manageScripts(...divs) { const newIndex = divs.findIndex(s => s === "show"); const prevIndex = lastVisibleIndex; scriptsArray.forEach((div, i) => { div.ontransitionend = null; div.onanimationend = null; div.classList.remove("enter-from-right", "enter-from-left"); if (i === newIndex) { div.classList.remove("unloaded"); requestAnimationFrame(() => { div.classList.remove("hide"); const enterClass = newIndex > prevIndex ? "enter-from-right" : "enter-from-left"; div.classList.add(enterClass); div.onanimationend = () => { div.classList.remove(enterClass); div.onanimationend = null; }; }); } else { requestAnimationFrame(() => { div.classList.add("hide"); }); div.ontransitionend = (e) => { if (e.propertyName === "opacity" && div.classList.contains("hide")) { div.classList.add("unloaded"); div.ontransitionend = null; } }; } }); lastVisibleIndex = newIndex >= 0 ? newIndex : lastVisibleIndex; } document.querySelector("#cancelDetailsButton").addEventListener("click", function () { manage_config_content(configCopy, false) }) function manageNewsStatus(patreonTier) { const generateNews = checkGenerableNews(patreonTier); if (generateNews === "yes") { const newsgenerationEnded = document.querySelector('.news-generation-ended'); if (newsgenerationEnded) { newsgenerationEnded.remove(); const newsGrid = document.createElement('div'); newsGrid.className = 'news-grid'; document.querySelector('#news').appendChild(newsGrid); generateNews(); } } } function checkGenerableNews(patreonTier) { let canGenerate = "no"; newsAvailable.normal = false; newsAvailable.turning = false; if (patreonTier.paidMember) { canGenerate = "yes"; if (patreonTier.tier === "Insider" || patreonTier.tier === "Founder") { newsAvailable.normal = true; newsAvailable.turning = true; } else if (patreonTier.tier === "Backer") { newsAvailable.normal = true; newsAvailable.turning = false; } } return canGenerate; } async function checkOpenSlideUp() { const tier = await getUserTier(); if (tier.paidMember) return; const lastShownStr = localStorage.getItem('patreonModalLastShown'); if (!canShowPatreonModal(lastShownStr)) { return; } const delaySec = 5; setTimeout(() => { showPatreonModal(); localStorage.setItem('patreonModalLastShown', new Date().toISOString()); }, delaySec * 1000); } function showPatreonModal() { patreonLogo.classList.add("open-slide-up") setTimeout(() => { patreonSlideUp.classList.add("open") }, 350); } slideUpClose.addEventListener('click', () => { patreonSlideUp.classList.remove("open"); patreonLogo.className = "bi-custom-patreon close-slide-up" }); function canShowPatreonModal(lastShown) { if (!lastShown) return true; // Nunca se mostró, podemos mostrarlo const last = new Date(lastShown).getTime(); const now = Date.now(); const diffDays = (now - last) / (1000 * 60 * 60 * 24); return diffDays >= 1; } init_colors_dict() document.addEventListener('DOMContentLoaded', async () => { const hostname = window.location.hostname; const isNightly = hostname.includes("nightly"); isNightlyHost = isNightly; versionNow = APP_VERSION; syncNightlyIndicator(); if (isNightly) { const favicon = document.querySelector('link[rel="icon"]'); //testing if (favicon) favicon.href = "../assets/images/logoNightly.png"; const tierInfo = await getUserTier(); let restrictionMessage = null; const insiderOrFounder = tierInfo.tier === "Insider" || tierInfo.tier === "Founder"; if (tierInfo.isLoggedIn && !insiderOrFounder) { restrictionMessage = "Please upgrade to the Insider or Founder tier on Patreon to access the nightly version."; } else if (!tierInfo.isLoggedIn) { restrictionMessage = "Please log in with your Patreon account to access the nightly version."; } if (restrictionMessage !== null) { nightlyBlock = true; const dropDiv = document.querySelector(".drop-div"); dropDiv.removeEventListener("dragover", handleDragOver); dropDiv.removeEventListener("dragenter", handleDragEnter); dropDiv.removeEventListener("dragleave", handleDragLeave); dropDiv.removeEventListener("drop", handleDrop); document.getElementById("statusIcon").className = "bi bi-lock"; document.getElementById("statusTitle").textContent = "Nightly version is only available for patrons."; document.getElementById("statusDesc").textContent = restrictionMessage; const recentsContainer = document.querySelector(".recents-container"); if (recentsContainer) recentsContainer.remove(); document.querySelectorAll(".script-view").forEach(div => { div.remove(); }); } const now = new Date(); const day = String(now.getDate()).padStart(2, '0'); const month = String(now.getMonth() + 1).padStart(2, '0'); const year = String(now.getFullYear()); const shortBuildId = BUILD_ID.startsWith("dpl_") ? BUILD_ID.replace("dpl_", "").slice(0, 7) : BUILD_ID.slice(0, 7); versionNow = `${APP_VERSION.replace("-dev", "")}.nightly.${day}-${month}-${year}.${shortBuildId}`; //remove -dev from APP_VERSION versionPanel.classList.add("nightly"); } updateToolbarThemeLogo(); syncNightlyThemeVisibility(); updateRateLimitsDisplay(); const storedVersion = localStorage.getItem('lastVersion'); // Última versión guardada versionPanel.textContent = `${versionNow}`; versionBadge.textContent = `Version ${versionNow}`; parchModalTitle.textContent = "Version " + versionNow + " patch notes" getPatchNotes() if (shouldShowPatchModal(storedVersion, versionNow)) { localStorage.setItem('lastVersion', versionNow); // Guardar nueva versión const patchModal = new bootstrap.Modal(document.getElementById('patchModal')); patchModal.show(); } let recents = await getRecentHandles(); populateRecentHandles(recents); let phrases = [ "Change the contract of every staff available in game", "Customize your calendar however you want it", "Edit the attributes of each driver just how you want them", "Create your own custom engines", "Get stories from your save using AI", "Compare drivers and teams with detailed graphs", "Modify car performance to your liking", "Fix game-breaking issues with ease", "No installation required, works in your browser", "Honda, for the love of god, give Alonso a good engine for once", "In memory of Aloy" ]; //reorder them randomly phrases = phrases.sort(() => Math.random() - 0.5); const animatedText = document.getElementById('animatedText'); const fakeText = document.querySelector('.fake-text'); let phraseIndex = 0; const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms)); async function animateTextLoop() { while (true) { const currentPhrase = phrases[phraseIndex]; fakeText.textContent = currentPhrase; // Typing phase for (let i = 0; i < currentPhrase.length; i++) { const char = currentPhrase[i]; const span = document.createElement('span'); span.className = 'char'; span.textContent = char; animatedText.appendChild(span); await sleep(10); // Typing speed } // Wait phase (read time) await sleep(5000); // Deleting phase while (animatedText.firstChild) { if (animatedText.lastChild) { animatedText.removeChild(animatedText.lastChild); } await sleep(8); // Deleting speed } // Move to next phrase phraseIndex = (phraseIndex + 1) % phrases.length; // Small pause before typing next one await sleep(200); } } // Clear initial text and start animation loop animatedText.innerHTML = ''; animateTextLoop(); }); export async function updateRateLimitsDisplay() { try { const res = await fetch("/api/usage-today"); if (!res.ok) return; const { used, limit, percentage } = await res.json(); const fill = document.getElementById("limitBarFill"); const text = document.getElementById("limitText"); const container = document.getElementById("rateLimitContainer"); //100% corresponds to not using any fill.style.width = `${100 - percentage}%`; // limpiar estados previos container.classList.remove( "rate-ok", "rate-warning", "rate-danger", "rate-blocked" ); let message = ""; let state = ""; if (percentage === 0) { state = "rate-ok"; message = "All requests available"; } else if (percentage < 50) { state = "rate-ok"; message = "Plenty of requests available"; } else if (percentage < 80) { state = "rate-warning"; message = "You're halfway through today's limit"; } else if (percentage < 100) { state = "rate-danger"; message = "Only a few requests left today"; } else { state = "rate-blocked"; message = "Daily limit reached"; } container.classList.add(state); text.textContent = message; } catch (err) { console.error("Failed to update rate limits display:", err); } } const MS_PER_DAY = 24 * 60 * 60 * 1000; function getRecentsTimeLabel(openedDate, now = new Date()) { const startNow = new Date(now.getFullYear(), now.getMonth(), now.getDate()); const startOpened = new Date(openedDate.getFullYear(), openedDate.getMonth(), openedDate.getDate()); let diffDays = Math.round((startNow - startOpened) / MS_PER_DAY); if (diffDays < 0) diffDays = 0; if (diffDays === 0) { return "Today"; } if (diffDays === 1) { return "Yesterday"; } return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`; } function populateRecentHandles(recents) { if (recents.length === 0) { const recentsContainer = document.querySelector(".recents-container"); if (recentsContainer) recentsContainer.classList.add("d-none"); return; } const recentList = document.getElementById("recentsList"); recentList.innerHTML = ""; // Clear existing items recents.forEach(handle => { const listItem = document.createElement("div"); listItem.className = "recent-file"; const fileName = document.createElement("span"); fileName.classList.add("file-name"); fileName.textContent = handle.name; fileName.addEventListener("click", async () => { const fileHandle = handle.handle; const hasPermission = await verifyPermission(fileHandle, false); if (!hasPermission) { console.error("No permission to access the file:", handle.name); return; } const file = await fileHandle.getFile(); await saveHandleToRecents(fileHandle); handle.lastOpened = new Date(); updateTimeLabel(); processSaveFile(file); }); const lastOpened = document.createElement("span"); lastOpened.classList.add("last-opened-time"); const updateTimeLabel = () => { const openedDate = new Date(handle.lastOpened); lastOpened.textContent = getRecentsTimeLabel(openedDate); }; updateTimeLabel(); lastOpened.addEventListener("mouseenter", () => { lastOpened.textContent = "Remove"; }); lastOpened.addEventListener("mouseleave", () => { updateTimeLabel(); }); lastOpened.addEventListener("click", async () => { await removeRecentHandle(handle.name); listItem.remove(); if (recentList.children.length === 0) { const recentsContainer = document.querySelector(".recents-container"); if (recentsContainer) recentsContainer.classList.add("d-none"); } }); listItem.appendChild(fileName); listItem.appendChild(lastOpened); recentList.appendChild(listItem); }); } async function verifyPermission(fileHandle) { const options = { mode: 'read' }; if ((await fileHandle.queryPermission(options)) === 'granted') { return true; } if ((await fileHandle.requestPermission(options)) === 'granted') { return true; } return false; } function createMarqueeItem(name, tier) { const span = document.createElement("span"); span.textContent = name; span.classList.add(tier); return span; } function updateToolbarThemeLogo() { const logoImg = document.querySelector(".toolbar-logo"); if (!logoImg) return; Object.values(themeToolbarLogos).forEach((meta) => { if (meta?.className) logoImg.classList.remove(meta.className); }); if (!hasPatreonThemeAccess) { logoImg.src = "../assets/images/logoVector.svg"; return; } const bodyThemeClass = Array.from(document.body.classList).find(className => className.endsWith("-theme")); const appliedTheme = (bodyThemeClass || selectedTheme || "").toLowerCase(); const themeKey = Object.keys(themeToolbarLogos).find((key) => appliedTheme.includes(key.replace("-theme", ""))); if (themeKey) { const meta = themeToolbarLogos[themeKey]; logoImg.src = meta.src; if (meta.className) logoImg.classList.add(meta.className); return; } logoImg.src = "../assets/images/logoVector.svg"; } function syncNightlyIndicator() { const titleEl = document.querySelector(".toolbar-title"); if (!titleEl) return; const shouldShow = isNightlyHost; titleEl.classList.toggle("nightly", shouldShow); const existingIcon = titleEl.querySelector(".nightly-icon"); if (shouldShow) { if (!existingIcon) { const moonIcon = document.createElement("i"); moonIcon.className = "bi bi-moon-fill nightly-icon"; titleEl.appendChild(moonIcon); } } else { if (existingIcon) existingIcon.remove(); } } function syncNightlyThemeVisibility() { const nightlyCard = document.querySelector('.one-theme[data-theme="nightly-theme"]'); if (!nightlyCard) return; const showNightlyTheme = isNightlyHost && hasPatreonThemeAccess; nightlyCard.classList.toggle("d-none", !showNightlyTheme); if (!showNightlyTheme && selectedTheme === "nightly-theme") { selectedTheme = "default-theme"; localStorage.removeItem("theme"); document.body.className = "font default-theme"; init_colors_dict(selectedTheme); updateToolbarThemeLogo(); } } document.querySelectorAll(".one-theme").forEach(function (elem) { elem.addEventListener("click", function () { if (!hasPatreonThemeAccess) return; if (elem.dataset.theme === "nightly-theme" && !isNightlyHost) return; selectedTheme = elem.dataset.theme document.querySelector(".one-theme.active").classList.remove("active") elem.classList.add("active") changeTheme() }) }); function changeTheme() { document.querySelector("body").className = `font ${selectedTheme}` localStorage.setItem("theme", selectedTheme) init_colors_dict(selectedTheme) updateToolbarThemeLogo() syncNightlyIndicator() reload_performance_graph() reload_h2h_graphs() } function loadTheme() { let theme = localStorage.getItem("theme") const savedThemeButton = theme ? document.querySelector(`.one-theme[data-theme="${theme}"]`) : null; if (!theme && isNightlyHost && hasPatreonThemeAccess) { theme = "nightly-theme" } if (theme === "nightly-theme" && !isNightlyHost) { theme = null; localStorage.removeItem("theme"); } selectedTheme = savedThemeButton ? theme : (theme === "nightly-theme" ? "nightly-theme" : "default-theme") document.querySelector("body").className = `font ${selectedTheme}` const activeTheme = document.querySelector(".one-theme.active") if (activeTheme) { activeTheme.classList.remove("active") } const currentThemeButton = document.querySelector(`.one-theme[data-theme="${selectedTheme}"]`) if (currentThemeButton) { currentThemeButton.classList.add("active") } if (theme && !savedThemeButton) { localStorage.removeItem("theme") } init_colors_dict(selectedTheme) updateToolbarThemeLogo() syncNightlyIndicator() syncNightlyThemeVisibility() reload_performance_graph() reload_h2h_graphs() } document.getElementById('logButton').addEventListener('click', function () { const logs = window.getLogEntries(); const logWindow = window.open('', '_blank'); const doc = logWindow.document; const style = ` body { font-family: Arial, sans-serif; margin: 20px; } table { width: 100%; border-collapse: collapse; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f4f4f4; } .log { color: green; } .error { color: red; } pre { white-space: pre-wrap; word-break: break-word; max-width: 600px; } `; const head = doc.createElement('head'); const title = doc.createElement('title'); title.textContent = 'Log Console'; const styleTag = doc.createElement('style'); styleTag.textContent = style; head.appendChild(title); head.appendChild(styleTag); doc.head.appendChild(head); const body = doc.createElement('body'); const heading = document.createElement('h2'); heading.textContent = 'Logs'; const table = document.createElement('table'); const thead = document.createElement('thead'); const headerRow = document.createElement('tr'); ['Type', 'Message', 'Timestamp'].forEach(text => { const th = document.createElement('th'); th.textContent = text; headerRow.appendChild(th); }); thead.appendChild(headerRow); const tbody = document.createElement('tbody'); logs.forEach(log => { const row = document.createElement('tr'); const typeCell = document.createElement('td'); typeCell.textContent = log.type.toUpperCase(); typeCell.classList.add(log.type); const messageCell = document.createElement('td'); const pre = document.createElement('pre'); // Si el mensaje es un objeto, lo formateamos como JSON pre.textContent = log.message.map(msg => typeof msg === 'object' ? JSON.stringify(msg, null, 2) : msg ).join(' '); messageCell.appendChild(pre); const timestampCell = document.createElement('td'); timestampCell.textContent = new Date(log.timestamp).toLocaleString(); row.appendChild(typeCell); row.appendChild(messageCell); row.appendChild(timestampCell); tbody.appendChild(row); }); table.appendChild(thead); table.appendChild(tbody); body.appendChild(heading); body.appendChild(table); doc.body.appendChild(body); }); /** * Verifies if the patch modal should be shown * @param {string|null} storedVersion - Version stored in localStorage * @param {string} versionNow - Current version of the app * @returns {boolean} - True if the modal should be shown, false otherwise */ function shouldShowPatchModal(storedVersion, versionNow) { if (!storedVersion) return true; // Si no hay una versión guardada, mostrar el modal const storedParts = storedVersion.split('.').map(Number); const currentParts = versionNow.split('.').map(Number); return storedParts[0] < currentParts[0] || storedParts[1] < currentParts[1]; } document.querySelectorAll(".team-logo-container").forEach(function (elem) { elem.addEventListener("click", function () { let active = document.querySelector(".team-logo-container.active") managingTeamChanged = true if (active) { active.classList.remove("active") } elem.classList.add("active") }) }); export async function confirmModal({ title, body, confirmText, cancelText }) { const modalEl = document.getElementById('confirmModal'); const bsModal = new bootstrap.Modal(modalEl, { keyboard: false }); // Elementos const confirmTitle = modalEl.querySelector('.modal-title'); const confirmBody = modalEl.querySelector('.modal-body p'); const confirmBtn = modalEl.querySelector('.confirm-modal'); const cancelBtn = modalEl.querySelector('.close-modal'); if (confirmTitle) confirmTitle.textContent = title; if (confirmBody) confirmBody.textContent = body; if (confirmBtn) { if (confirmText) { confirmBtn.textContent = confirmText; confirmBtn.classList.remove('d-none'); } else { confirmBtn.classList.add('d-none'); } } if (cancelBtn) { if (cancelText) { cancelBtn.textContent = cancelText; cancelBtn.classList.remove('d-none'); } else { cancelBtn.classList.add('d-none'); } } return new Promise((resolve) => { let clicked = false; const controller = new AbortController(); const { signal } = controller; confirmBtn?.addEventListener('click', () => { clicked = true; resolve(true); bsModal.hide(); }, { once: true, signal }); cancelBtn?.addEventListener('click', () => { clicked = true; resolve(false); bsModal.hide(); }, { once: true, signal }); modalEl.addEventListener('hidden.bs.modal', () => { if (!clicked) resolve(false); controller.abort(); }, { once: true }); bsModal.show(); }); } document.querySelectorAll(".redesigned-dropdown").forEach(dropdown => { dropdown.addEventListener("click", function (e) { e.stopPropagation(); document.querySelectorAll(".redesigned-dropdown.open").forEach(openDropdown => { if (openDropdown !== dropdown) { openDropdown.classList.remove("open"); } }); dropdown.classList.toggle("open"); }); }); document.addEventListener("click", function () { document.querySelectorAll(".redesigned-dropdown.open").forEach(openDropdown => { openDropdown.classList.remove("open"); }); }); export function attachHold(btn, el, step = 1, opts = {}) { const min = opts.min ?? -Infinity; const max = opts.max ?? Infinity; const progressEl = opts.progressEl ?? null; const values = Array.isArray(opts.values) && opts.values.length ? opts.values.slice() : null; const loop = !!opts.loop; const onChange = typeof opts.onChange === 'function' ? opts.onChange : () => { }; // NUEVO: Permitimos pasar una función de formateo const format = opts.format || ((v) => v); const initialDelay = opts.initialDelay ?? 400; const tiers = opts.tiers ?? [ [0, 250], [750, 150], [1500, 80], [3000, 40], ]; let timer, start; const getText = () => (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') ? (el.value ?? '') : (el.innerText ?? ''); const setText = (txt) => { // NUEVO: Aplicamos el formato si es un número const valToDisplay = (typeof txt === 'number') ? format(txt) : txt; if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') { el.value = String(valToDisplay); el.dispatchEvent(new Event('input', { bubbles: true })); } else { const current = el.innerText || ''; if (/-?\d+(\.\d+)?/.test(current) && typeof txt === 'number') { // Nota: Aquí el replace es delicado con formatos, // para tu caso de Input suele bastar con la línea de arriba (el.value = ...) // pero si usas span, reemplazamos todo para evitar romper el formato parcial. el.innerText = String(valToDisplay); } else { el.innerText = String(valToDisplay); } } }; const getNum = () => { if (values) return NaN; let raw = getText(); // NUEVO: Eliminamos las comas antes de intentar parsear el número // Esto permite que "100,000" se convierta en "100000" para el cálculo raw = String(raw).replace(/,/g, ''); const m = raw.match(/-?\d+(\.\d+)?/); return m ? parseFloat(m[0]) : 0; }; const setNum = (val) => { const clamped = Math.max(min, Math.min(max, val)); setText(clamped); updateProgress(clamped); onChange(clamped, currentPercent(clamped)); // Devuelve el valor numérico limpio }; // ... El resto de funciones (findCurrentIndex, setIndex, etc.) se mantienen igual ... // Solo asegúrate de copiar el resto de tu lógica original aquí abajo. const findCurrentIndex = () => { const raw = String(getText()).trim(); let idx = values.findIndex(v => String(v) === raw); if (idx === -1) { const numMatch = raw.replace(/,/g, '').match(/-?\d+(\.\d+)?/); // Ajuste aquí también por si acaso if (numMatch) { const num = parseFloat(numMatch[0]); idx = values.findIndex(v => Number(v) === num); } } return idx === -1 ? 0 : idx; }; const setIndex = (i) => { const len = values.length; let next = i; if (loop) { next = ((i % len) + len) % len; } else { next = Math.max(0, Math.min(len - 1, i)); } const val = values[next]; setText(val); updateProgress(next, true); onChange(val, currentPercent(val, true)); return next; }; const pickInterval = (heldMs) => { let ms = tiers[0][1]; for (const [t, interval] of tiers) { if (heldMs >= t) ms = interval; else break; } return ms; }; const currentPercent = (valOrIdx, isIndex = false) => { if (values) { const len = values.length; if (len <= 1) return 100; const idx = isIndex ? valOrIdx : values.findIndex(v => String(v) === String(valOrIdx)); const i = idx < 0 ? 0 : idx; return Math.round((i / (len - 1)) * 100); } if (max > min) { const v = Number(valOrIdx); const p = ((v - min) / (max - min)) * 100; return Math.round(Math.max(0, Math.min(100, p))); } return 0; }; const updateProgress = (valOrIdx, isIndex = false) => { if (!progressEl) return; const p = currentPercent(valOrIdx, isIndex); progressEl.style.width = p + '%'; progressEl.ariaValueNow = String(p); }; const tick = () => { if (values) { const cur = findCurrentIndex(); setIndex(cur + (step >= 0 ? +1 : -1)); } else { const cur = getNum(); setNum(cur + step); } }; const startLoop = () => { start = performance.now(); tick(); const loopFn = () => { const held = performance.now() - start; timer = setTimeout(() => { tick(); loopFn(); }, pickInterval(held)); }; timer = setTimeout(loopFn, initialDelay); }; const stopLoop = () => { clearTimeout(timer); timer = null; }; const downEv = 'onpointerdown' in window ? 'pointerdown' : 'mousedown'; const upEv = 'onpointerup' in window ? 'pointerup' : 'mouseup'; const leaveEv = 'onpointerleave' in window ? 'pointerleave' : 'mouseleave'; const cancelEv = 'pointercancel'; btn.addEventListener(downEv, (e) => { e.preventDefault(); startLoop(); }); document.addEventListener(upEv, stopLoop, true); document.addEventListener(cancelEv, stopLoop, true); btn.addEventListener(leaveEv, stopLoop, true); } ================================================ FILE: src/js/frontend/seasonMods.js ================================================ import { set } from "idb-keyval"; import { Command } from "../backend/command.js"; import { applyConfigFromEditorUI, custom_team, replace_custom_team_logo, setRenaultEnginePresentation, updateJenzerToDams } from "./renderer.js"; let calendarEditMode = null, calendarEditMode2026 = null; let mods2026IlluminationTimeout = null; function normalizeToggleEnabled(value) { return value === true || value === 1 || value === "1"; } function clearMods2026Illumination() { if (mods2026IlluminationTimeout) { clearTimeout(mods2026IlluminationTimeout); mods2026IlluminationTimeout = null; } const mods2026View = document.getElementById("mods2026View"); if (mods2026View) mods2026View.classList.remove("illuminated"); } function seasonModsIsVisible() { const seasonModsDiv = document.getElementById("season_mods"); if (!seasonModsDiv) return false; return !seasonModsDiv.classList.contains("hide") && !seasonModsDiv.classList.contains("unloaded"); } function scheduleMods2026Illumination() { clearMods2026Illumination(); mods2026IlluminationTimeout = setTimeout(() => { mods2026IlluminationTimeout = null; if (!seasonModsIsVisible()) return; const mods2026Pill = document.getElementById("mods2026Pill"); if (mods2026Pill && !mods2026Pill.classList.contains("active")) return; const mods2026View = document.getElementById("mods2026View"); if (mods2026View && !mods2026View.classList.contains("d-none")) { mods2026View.classList.add("illuminated"); } }, 2200); } function setAduoTpTogglesChecked(enabled) { const aduoButton = document.querySelector("#mods2026View .change-aduo-tps-2026"); if (aduoButton) aduoButton.classList.toggle("completed", enabled); const settingsToggle = document.getElementById("aduoTPSToggleSettings"); if (settingsToggle) settingsToggle.checked = enabled; } function updateAduoTpEnabled(enabled) { setAduoTpTogglesChecked(enabled); const command = new Command("updateAduoTPEnabled", { enabled }); command.execute(); syncMods2026ApplyAllButtonState(); } function getCustomTeamName() { const teamNode = document.querySelector(".ct-teamname"); return String(teamNode?.dataset.teamshow || teamNode?.dataset.teamname || ""); } function shouldAutoApplyCadillacLogo() { if (!custom_team) return false; return getCustomTeamName().toLowerCase().includes("cadillac"); } let cadillacLogoDataUrlPromise = null; function getCadillacLogoDataUrl() { if (cadillacLogoDataUrlPromise) return cadillacLogoDataUrlPromise; cadillacLogoDataUrlPromise = fetch("../assets/images/logos/cadillac.png") .then((res) => (res.ok ? res.blob() : Promise.reject(new Error("Cadillac logo not found")))) .then((blob) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error || new Error("Failed to read Cadillac logo")); reader.readAsDataURL(blob); })) .catch(() => null); return cadillacLogoDataUrlPromise; } function tryApplyCadillacCustomLogo() { if (!shouldAutoApplyCadillacLogo()) return; getCadillacLogoDataUrl().then((dataUrl) => { if (!dataUrl) return; replace_custom_team_logo(String(dataUrl)); }); } function prefersReducedMotion() { return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches; } function animatePointsValue(element, targetValue, durationMs = 1000) { if (!element) return; const startValueRaw = Number.parseInt(String(element.textContent).replace(/[^\d-]/g, ""), 10); const startValue = startValueRaw const endValue = Number(targetValue); if (prefersReducedMotion() || durationMs <= 0) { element.textContent = String(endValue); return; } const token = String((Number(element.dataset.animToken || "0") || 0) + 1); element.dataset.animToken = token; const startTs = performance.now(); const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3); const tick = (now) => { if (element.dataset.animToken !== token) return; const t = Math.min(1, (now - startTs) / durationMs); const eased = easeOutCubic(t); const current = Math.round(startValue + (endValue - startValue) * eased); element.textContent = String(current); if (t < 1) requestAnimationFrame(tick); }; requestAnimationFrame(tick); } function setModsSeason(seasonYear) { const mods2025Pill = document.getElementById("mods2025Pill"); const mods2026Pill = document.getElementById("mods2026Pill"); const mods2025View = document.getElementById("mods2025View"); const mods2026View = document.getElementById("mods2026View"); if (!mods2025Pill || !mods2026Pill || !mods2025View || !mods2026View) return; const is2026 = String(seasonYear) === "2026"; mods2025Pill.classList.toggle("active", !is2026); mods2026Pill.classList.toggle("active", is2026); mods2025View.classList.toggle("d-none", is2026); mods2026View.classList.toggle("d-none", !is2026); if (!is2026) clearMods2026Illumination(); } function initModsSeasonPills() { const mods2025Pill = document.getElementById("mods2025Pill"); const mods2026Pill = document.getElementById("mods2026Pill"); if (!mods2025Pill || !mods2026Pill) return; if (mods2025Pill.dataset.modsInit === "1") return; mods2025Pill.dataset.modsInit = "1"; mods2025Pill.addEventListener("click", function (e) { e.preventDefault(); setModsSeason(2025); }); mods2026Pill.addEventListener("click", function (e) { e.preventDefault(); setModsSeason(2026); scheduleMods2026Illumination(); }); setModsSeason(2026); } function initMods2026Actions(){ const mods2026View = document.getElementById("mods2026View"); if (!mods2026View) return; if (mods2026View.dataset.modsActionsInit === "1") return; mods2026View.dataset.modsActionsInit = "1"; const timeTravelButton = mods2026View.querySelector(".time-travel-2026"); if (timeTravelButton) { timeTravelButton.addEventListener("click", function () { const command = new Command("timeTravel", { dayNumber: 46019, mod: "2026" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); }); } const changeCalendarButton = mods2026View.querySelector(".change-calendar-2026"); if (changeCalendarButton) { changeCalendarButton.addEventListener("click", function () { if (!calendarEditMode2026) return; const command = new Command("changeCalendar", { type: calendarEditMode2026, mod: "2026" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); }); } const add2025Results = mods2026View.querySelector(".add-results-2026"); const add2025ResultsPoints = Array.from(mods2026View.querySelectorAll(".add-results-points[data-target]")); const setAdd2025ResultsPoints = (animate = false) => { add2025ResultsPoints.forEach((el) => { el.classList.add("activated") const target = Number(el.dataset.target); if (animate) animatePointsValue(el, target, 1000); else el.textContent = String(target); }); }; if (add2025Results) { if (add2025Results.classList.contains("completed")) { setAdd2025ResultsPoints(false); } const mo = new MutationObserver(() => { if (add2025Results.classList.contains("completed")) { setAdd2025ResultsPoints(false); } }); mo.observe(add2025Results, { attributes: true, attributeFilter: ["class"] }); add2025Results.addEventListener("click", function () { setAdd2025ResultsPoints(true); const command = new Command("changeCfd", {mod: "2026"}); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); }); } const changeRegulationsButton = mods2026View.querySelector(".change-regulations-2026"); if (changeRegulationsButton) { changeRegulationsButton.addEventListener("click", function () { if (this.classList.contains("completed") || this.dataset.running === "1") return; this.dataset.running = "1"; this.classList.add("disabled"); const command = new Command("changeRegulations", {mod: "2026"}); try { command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); } finally { this.dataset.running = "0"; this.classList.remove("disabled"); const command2 = new Command("regulationsRefresh", {}); command2.execute(); } }); } const changePerformanceButton2026 = mods2026View.querySelector(".change-performance-2026"); if (changePerformanceButton2026) { const syncJenzerDamsState = () => { updateJenzerToDams(changePerformanceButton2026.classList.contains("completed") ? "dams" : "jenzer"); }; // Ensure the UI-dependent state is correct when the mod was already applied // (e.g. after "Mod data fetched" marks the button as completed). syncJenzerDamsState(); const mo = new MutationObserver(() => syncJenzerDamsState()); mo.observe(changePerformanceButton2026, { attributes: true, attributeFilter: ["class"] }); changePerformanceButton2026.addEventListener("click", function () { tryApplyCadillacCustomLogo(); const alfaReplaceButton = document.querySelector("#alfaReplaceButton button"); if (alfaReplaceButton && alfaReplaceButton.dataset.value === "stake") { applyConfigFromEditorUI({ alfa: "audi" }); } const command = new Command("add2026Engines", {mod: "2026"}); command.execute(); setRenaultEnginePresentation("honda"); updateJenzerToDams("dams"); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); const command2 = new Command("changePerformance", {mod: "2026"}); command2.execute(); }); } const changeStatsButton = mods2026View.querySelector(".change-stats-2026"); if (changeStatsButton) { changeStatsButton.addEventListener("click", function () { const command = new Command("changeStats", {mod: "2026"}); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); }); } const extraDriversButton = mods2026View.querySelector(".extra-drivers-2026"); if (extraDriversButton) { extraDriversButton.addEventListener("click", function () { const command = new Command("extraDrivers", {mod: "2026"}); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026Dependencies(); syncMods2026ApplyAllButtonState(); }); } const driverLineUpsButton = mods2026View.querySelector(".change-line-ups-2026"); if (driverLineUpsButton) { driverLineUpsButton.addEventListener("click", function () { const command = new Command("changeLineUps", {mod: "2026"}); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2026ApplyAllButtonState(); document.querySelector("#mods2026View .had-ovr").classList.remove("atfont"); document.querySelector("#mods2026View .had-ovr").classList.add("rbfont"); document.querySelector("#mods2026View .ant-ovr").classList.add("mefont"); }); } const aduoToggle = mods2026View.querySelector(".change-aduo-tps-2026"); if (aduoToggle) { aduoToggle.addEventListener("click", function () { this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; updateAduoTpEnabled(this.classList.contains("completed")); }); } const settingsToggle = document.getElementById("aduoTPSToggleSettings"); if (settingsToggle && settingsToggle.dataset.aduoInit !== "1") { settingsToggle.dataset.aduoInit = "1"; settingsToggle.addEventListener("change", function () { updateAduoTpEnabled(this.checked); }); } const applyAllButton = mods2026View.querySelector(".apply-all-2026"); if (applyAllButton) { applyAllButton.addEventListener("click", function () { if (applyAllButton.dataset.running === "1") return; // anti-bucle applyAllButton.dataset.running = "1"; const clickNext = () => { syncMods2026Dependencies(); const btn = mods2026View.querySelector(".one-change-button:not(.completed):not(.disabled)"); if (!btn) { if (aduoToggle && !aduoToggle.classList.contains("completed")) { aduoToggle.click(); } applyAllButton.dataset.running = "0"; syncMods2026ApplyAllButtonState(); return; } btn.click(); setTimeout(clickNext, 300); }; clickNext(); }, { once: true }); // evita listeners duplicados } syncMods2026Dependencies(); syncMods2026ApplyAllButtonState(); } function initMods2025Actions() { const mods2025View = document.getElementById("mods2025View"); if (!mods2025View) return; if (mods2025View.dataset.modsActionsInit === "1") return; mods2025View.dataset.modsActionsInit = "1"; const timeTravelButton = mods2025View.querySelector(".time-travel"); if (timeTravelButton) { timeTravelButton.addEventListener("click", function () { const command = new Command("timeTravel", { dayNumber: 45657, mod: "2025" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const changeLineUpsButton = mods2025View.querySelector(".change-line-ups"); if (changeLineUpsButton) { changeLineUpsButton.addEventListener("click", function () { const command = new Command("changeLineUps", { mod: "2025" }); command.execute(); const hamTransfer = mods2025View.querySelector(".ham-transfer"); const saiTransfer = mods2025View.querySelector(".sai-transfer"); const antTransfer = mods2025View.querySelector(".ant-transfer"); const antOvr = mods2025View.querySelector(".ant-ovr"); const borOvr = mods2025View.querySelector(".bor-ovr"); if (hamTransfer) hamTransfer.classList.remove("mefont"); if (saiTransfer) saiTransfer.classList.remove("fefont"); if (hamTransfer) hamTransfer.classList.add("fefont"); if (saiTransfer) saiTransfer.classList.add("wifont"); if (antTransfer) antTransfer.classList.add("mefont"); if (antOvr) antOvr.classList.add("mefont"); if (borOvr) borOvr.classList.remove("mcfont"); if (borOvr) borOvr.classList.add("affont"); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const changeStatsButton = mods2025View.querySelector(".change-stats"); if (changeStatsButton) { changeStatsButton.addEventListener("click", function () { const command = new Command("changeStats", { mod: "2025" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const changeCfdButton = mods2025View.querySelector(".change-cfd"); if (changeCfdButton) { changeCfdButton.addEventListener("click", function () { const command = new Command("changeCfd", {mod : "2025"}); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const changeRegulationsButton = mods2025View.querySelector(".change-regulations"); if (changeRegulationsButton) { changeRegulationsButton.addEventListener("click", function () { const command = new Command("changeRegulations", { mod: "2025" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const extraDriversButton = mods2025View.querySelector(".extra-drivers"); if (extraDriversButton) { extraDriversButton.addEventListener("click", function () { const command = new Command("extraDrivers", { mod: "2025" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; syncMods2025Dependencies(); }); } const changeCalendarButton = mods2025View.querySelector(".change-calendar"); if (changeCalendarButton) { changeCalendarButton.addEventListener("click", function () { if (!calendarEditMode) return; const command = new Command("changeCalendar", { type: calendarEditMode, mod: "2025" }); command.execute(); this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } const changePerformanceButton = mods2025View.querySelector(".change-performance"); if (changePerformanceButton) { changePerformanceButton.addEventListener("click", function () { const command = new Command("changePerformance", { mod: "2025" }); command.execute(); const mclaren = mods2025View.querySelector(".mclaren-performance"); const redbull = mods2025View.querySelector(".redbull-performance"); const williams = mods2025View.querySelector(".williams-performance"); if (mclaren) mclaren.innerText = "63.7%"; if (redbull) redbull.innerText = "59.4%"; if (williams) williams.innerText = "56.8%"; this.classList.add("completed"); this.querySelector("span").textContent = "Applied"; }); } } export function initSeasonMods() { const seasonModsDiv = document.getElementById("season_mods"); if (!seasonModsDiv) return; initModsSeasonPills(); initMods2025Actions(); initMods2026Actions(); if (seasonModsDiv.dataset.modsIllumInit !== "1") { seasonModsDiv.dataset.modsIllumInit = "1"; let wasVisible = seasonModsIsVisible(); const syncVisibility = () => { const visible = seasonModsIsVisible(); if (visible === wasVisible) return; wasVisible = visible; if (!visible) { clearMods2026Illumination(); return; } const mods2026Pill = document.getElementById("mods2026Pill"); if (mods2026Pill && mods2026Pill.classList.contains("active")) { scheduleMods2026Illumination(); } }; const mo = new MutationObserver(syncVisibility); mo.observe(seasonModsDiv, { attributes: true, attributeFilter: ["class"] }); if (wasVisible) { const mods2026Pill = document.getElementById("mods2026Pill"); if (mods2026Pill && mods2026Pill.classList.contains("active")) { scheduleMods2026Illumination(); } } } } export function syncAduoTpToggles(enabledRaw) { const enabled = normalizeToggleEnabled(enabledRaw); setAduoTpTogglesChecked(enabled); syncMods2026ApplyAllButtonState(); } export function syncMods2026ApplyAllButtonState() { const mods2026View = document.getElementById("mods2026View"); if (!mods2026View) return; const applyAllButton = mods2026View.querySelector(".apply-all-2026"); if (!applyAllButton) return; const applyAllText = applyAllButton.querySelector("span"); const aduoToggle = mods2026View.querySelector(".change-aduo-tps-2026"); const remaining = mods2026View.querySelectorAll( ".one-change-button:not(.completed):not(.disabled)" ).length; const allApplied = remaining === 0 && (!aduoToggle || aduoToggle.classList.contains("completed")); applyAllButton.classList.toggle("applied", allApplied); if (applyAllText) applyAllText.textContent = allApplied ? "Applied" : "Apply all"; } export function syncMods2025Dependencies() { const mods2025View = document.getElementById("mods2025View"); if (!mods2025View) return; const extraDriversButton = mods2025View.querySelector(".extra-drivers"); const lineUpsButton = mods2025View.querySelector(".change-line-ups"); if (!lineUpsButton) return; if (lineUpsButton.classList.contains("completed")) { lineUpsButton.classList.remove("disabled"); return; } const hasExtraDrivers = !!(extraDriversButton && extraDriversButton.classList.contains("completed")); const lineUpsText = lineUpsButton.querySelector("span"); lineUpsButton.classList.toggle("disabled", !hasExtraDrivers); if (lineUpsText) { lineUpsText.textContent = hasExtraDrivers ? "Apply" : "Requires extra drivers"; } } export function syncMods2026Dependencies() { const mods2026View = document.getElementById("mods2026View"); if (!mods2026View) return; const extraDriversButton = mods2026View.querySelector(".extra-drivers-2026"); const lineUpsButton = mods2026View.querySelector(".change-line-ups-2026"); if (!lineUpsButton) return; if (lineUpsButton.classList.contains("completed")) { lineUpsButton.classList.remove("disabled"); return; } const hasExtraDrivers = !!(extraDriversButton && extraDriversButton.classList.contains("completed")); const lineUpsText = lineUpsButton.querySelector("span"); lineUpsButton.classList.toggle("disabled", !hasExtraDrivers); if (lineUpsText) { lineUpsText.textContent = hasExtraDrivers ? "Apply" : "Requires extra drivers"; } } export function updateMod2026Blocking(data) { const mods2026View = document.getElementById("mods2026View"); if (!mods2026View) return; const timeTravelButton = mods2026View.querySelector(".time-travel-2026"); const timeTravelText = timeTravelButton ? timeTravelButton.querySelector("span") : null; const changeCalendarButton = mods2026View.querySelector(".change-calendar-2026"); const changeCalendarText = changeCalendarButton ? changeCalendarButton.querySelector("span") : null; const modBlocking = mods2026View.querySelector(".mod-blocking.mods-2026-blocking"); const changesGrid = mods2026View.querySelector(".grid-and-downloads"); const recommendedDownloads = mods2026View.querySelector(".recommended-downloads"); calendarEditMode2026 = null; const allowTimeTravel = data === "Start2024" || data === "AlreadyEdited"; if (timeTravelButton && !timeTravelButton.classList.contains("completed")) { timeTravelButton.classList.toggle("disabled", !allowTimeTravel); if (timeTravelText) timeTravelText.textContent = allowTimeTravel ? "Apply" : "Disabled"; } const allowCalendarEdit = data === "Start2024" || data === "AlreadyEdited"; if (allowCalendarEdit) { calendarEditMode2026 = data; } if (changeCalendarButton && !changeCalendarButton.classList.contains("completed")) { changeCalendarButton.classList.toggle("disabled", !allowCalendarEdit); if (changeCalendarText) changeCalendarText.textContent = allowCalendarEdit ? "Apply" : "Disabled"; } if (!modBlocking) return; if (data === "AlreadyEdited" || data === "Start2024" ) { modBlocking.classList.add("d-none"); changesGrid.classList.remove("d-none"); recommendedDownloads.classList.remove("d-none"); } else { modBlocking.classList.remove("d-none"); changesGrid.classList.add("d-none"); recommendedDownloads.classList.add("d-none"); } } export function updateMod2025Blocking(data) { const mods2025View = document.getElementById("mods2025View"); if (!mods2025View) return; const modBlocking = mods2025View.querySelector(".mod-blocking"); const changesGrid = mods2025View.querySelector(".changes-grid"); const timeTravelButton = mods2025View.querySelector(".time-travel"); const timeTravelText = timeTravelButton ? timeTravelButton.querySelector("span") : null; if (!modBlocking || !changesGrid) return; if (data === "AlreadyEdited") { modBlocking.classList.add("d-none"); changesGrid.classList.remove("d-none"); } else if (data === "Start2024") { modBlocking.classList.add("d-none"); changesGrid.classList.remove("d-none"); if (timeTravelButton) timeTravelButton.classList.remove("disabled"); if (timeTravelText) timeTravelText.textContent = "Apply"; calendarEditMode = data; } else if (data === "Direct2025" || data === "End2024") { modBlocking.classList.add("d-none"); changesGrid.classList.remove("d-none"); if (timeTravelButton) timeTravelButton.classList.add("disabled"); if (timeTravelText) timeTravelText.textContent = "Disabled"; calendarEditMode = data; } else { modBlocking.classList.remove("d-none"); changesGrid.classList.add("d-none"); } } ================================================ FILE: src/js/frontend/seasonViewer.js ================================================ import { races_names, names_full, team_dict, codes_dict, countries_data, combined_dict, logos_disc, races_map, driversTableLogosDict, f1_teams, f2_teams, f3_teams } from "./config"; import { resetH2H, queueAutoCompareDrivers } from './head2head'; import { game_version, custom_team, manageSaveButton, new_update_notifications, seasonModData, updateFront } from "./renderer"; import { insert_space, manageColor, setCurrentSeason, format_name } from "./transfers"; import { news_insert_space } from "../backend/scriptUtils/newsUtils.js"; import { Command } from "../backend/command.js"; let seasonTable; let teamsTable; let races_ids = [] let seasonResults; let calendarData; let pointsOrPos = "points" let currentFormula = 1 let alphaReplace = "alphatauri" let alpineReplace = "alpine" let alfaReplace = "alfa" let redbullReplace = "redbull" let astonReplace = "aston" let williamsReplace = "williams" let haasReplace = "haas" let driverOrTeams = "drivers" let isYearSelected = false let racesLeftCount = 0, sprintsLeft = 0; export let engine_allocations; let driverCells; let teamCells; let standingsDetailsEnabled = false; let qualifyingHeightListenerAttached = false; let winsHeightListenerAttached = false; let driversStandingsHeightListenerAttached = false; let teamsStandingsHeightListenerAttached = false; let comparisonsHeightListenerAttached = false; const sessionResultsEventsCache = new Map(); let sessionResultsActiveGpAnchor = null; let sessionResultsActiveGpMeta = null; let sessionResultsLastFetched = null; let sessionResultsEditMode = false; let sessionResultsDraggedRow = null; let sessionResultsDragRaf = 0; let sessionResultsDragY = 0; let sessionResultsPointsInfo = null; let sessionResultsPointsInfoPromise = null; let sessionResultsCompactMode = false; function ensureDropdownCheckIcons(menuEl) { if (!menuEl) return; menuEl.querySelectorAll(".redesigned-dropdown-item").forEach((item) => { if (item.dataset.noCheck === "1") return; if (item.closest(".dropdown-submenu-menu")) return; if (item.querySelector("i.bi-check")) return; const icon = document.createElement("i"); icon.classList.add("bi", "bi-check", "unactive"); item.appendChild(icon); }); } function syncDropdownCheckIcons(menuEl, isSelected) { if (!menuEl || typeof isSelected !== "function") return; ensureDropdownCheckIcons(menuEl); menuEl.querySelectorAll(".redesigned-dropdown-item").forEach((item) => { const selected = Boolean(isSelected(item)); item.querySelector("i.bi-check")?.classList.toggle("unactive", !selected); }); } function syncTableTypeDropdownChecks() { const menu = document.getElementById("tableTypeDropdown"); syncDropdownCheckIcons(menu, (item) => item.dataset.value === String(pointsOrPos)); } function syncSeriesTypeDropdownChecks() { const menu = document.getElementById("seriesTypeDropdown"); const selected = document.getElementById("seriesTypeButton")?.dataset?.value ?? String(currentFormula); syncDropdownCheckIcons(menu, (item) => item.dataset.value === String(selected)); } function syncRecordsTypeDropdownChecks() { const menu = document.getElementById("recordsTypeDropdown"); const selected = document.getElementById("recordsTypeButton")?.dataset?.value; syncDropdownCheckIcons(menu, (item) => item.dataset.value === String(selected)); } function syncYearDropdownChecks() { const menu = document.getElementById("yearMenu"); const selected = document.getElementById("yearButton")?.dataset?.year; syncDropdownCheckIcons(menu, (item) => item.dataset.year === String(selected)); } function applyStandingsDetailsState() { const seasonViewer = document.getElementById("season_viewer"); const button = document.getElementById("standingsDetailsButton"); const label = button?.querySelector("span"); const eyeIcon = button?.querySelector("i.bi-eye"); const eyeSlashIcon = button?.querySelector("i.bi-eye-slash"); if (seasonViewer) { seasonViewer.classList.toggle("standings-details-enabled", standingsDetailsEnabled); } if (label) { label.textContent = standingsDetailsEnabled ? "Hide details" : "Show details"; } if (eyeIcon && eyeSlashIcon) { eyeIcon.style.display = standingsDetailsEnabled ? "inline" : "none"; eyeSlashIcon.style.display = standingsDetailsEnabled ? "none" : "inline"; } } function setStandingsPositionChange(changeDiv, lastPositionChange) { if (!changeDiv) return; const numberEl = changeDiv.querySelector(".standings-pos-change-number"); const iconEl = changeDiv.querySelector("i"); // In DB: negative = gained positions, positive = lost positions. // UI: positive = gained, negative = lost. const dbChange = Number(lastPositionChange) || 0; const change = -dbChange; changeDiv.classList.remove("up", "down", "neutral"); if (numberEl) { numberEl.textContent = `${Math.abs(change)}`; } if (iconEl) { if (change > 0) { iconEl.className = "bi bi-caret-up-fill"; changeDiv.classList.add("up"); } else if (change < 0) { iconEl.className = "bi bi-caret-down-fill"; changeDiv.classList.add("down"); } else { iconEl.className = "bi bi-dash"; changeDiv.classList.add("neutral"); } } } function setStandingsPointsGap(gapDiv, gapToLeader) { if (!gapDiv) return; const gap = Number(gapToLeader); gapDiv.textContent = gap === 0 ? "0" : `+${Math.max(0, gap)}`; } function updateStandingsPointsGaps(rows, leaderPoints) { rows.forEach((row) => { const gap = Number(leaderPoints) - Number(row.points); setStandingsPointsGap(row.pointsGapDiv, gap); }); } document.getElementById("standingsDetailsButton").addEventListener("click", function () { standingsDetailsEnabled = !standingsDetailsEnabled; applyStandingsDetailsState(); }); applyStandingsDetailsState(); export let engine_names = { //this one is changed as the user adds engines, so it will stayhere 1: "Ferrari", 4: "Rbpt", 7: "Mercedes", 10: "Renault" } export function addEngineName(id, name) { engine_names[id] = name } export function deleteEngineName(id) { delete engine_names[id] } export function setEngineAllocations(allocations) { engine_allocations = allocations } export function getEngineLogoSrc(name) { const normalized = String(name || "").toLowerCase() if (normalized.includes("ferrari")) return "../assets/images/logos/ferrari.png" if (normalized.includes("rbpt") || normalized.includes("red bull")) return "../assets/images/logos/redbull.png" if (normalized.includes("mercedes")) return "../assets/images/logos/mercedes.png" if (normalized.includes("renault")) return "../assets/images/logos/renault.png" if (normalized.includes("honda")) return "../assets/images/logos/honda.png" if (normalized.includes("audi")) return "../assets/images/logos/audi.png" if (normalized.includes("ford")) return "../assets/images/logos/ford.png" if (normalized.includes("bmw")) return "../assets/images/logos/bmw.png" if (normalized.includes("porsche")) return "../assets/images/logos/porsche.png" if (normalized.includes("toyota")) return "../assets/images/logos/toyota.png" //if name contains baby we should return this emoji ☺️ if (normalized.includes("babi")) return emojiToDataUri("☺️"); return "../assets/images/engine.png" } function emojiToDataUri(emoji) { const svg = ` ${emoji} `.trim(); return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`; } export function resetViewer() { if (seasonTable) { seasonTable.destroy() } pointsOrPos = "points" if (teamsTable) { teamsTable.destroy() } } export function resetYearButtons() { document.getElementById("yearButton").textContent = "Year" isYearSelected = false manage_show_tables() document.getElementById("yearButtonH2H").textContent = "Year" document.getElementById("yearPredictionButton").textContent = "Year" document.getElementById("yearPredictionModalButton").textContent = "Year" } /** * Pills for the drivers and teams tables */ document.getElementById("driverspill").addEventListener("click", function () { driverOrTeams = "drivers" manageDriversTeamsModeChanged() }) document.getElementById("teamspill").addEventListener("click", function () { driverOrTeams = "teams" manageDriversTeamsModeChanged() }) function manageDriversTeamsModeChanged() { updateAllTimeVisibilityForTeamsRecords(); updateTopPanelControlsVisibility(); const typeVal = document.querySelector("#recordsTypeButton")?.dataset?.value; if (typeVal === "standings") { manage_show_tables(); return; } if (typeVal === "seasonreview") { return; } manageRecordsSelected(null); } function updateAllTimeVisibilityForTeamsRecords() { const allTime = document.getElementById("allTimeRecords"); if (!allTime) return; const typeVal = document.querySelector("#recordsTypeButton")?.dataset?.value; const allowAllTime = currentFormula === 1 && driverOrTeams !== "teams" && typeVal !== "standings" && typeVal !== "seasonreview" && typeVal !== "sessionresults"; allTime.classList.toggle("d-none", !allowAllTime); } function updateTopPanelControlsVisibility() { const typeVal = document.querySelector("#recordsTypeButton")?.dataset?.value; const showStandingsOnlyControls = typeVal === "standings"; const hideDriversTeamsPills = typeVal === "seasonreview" || typeVal === "sessionresults"; const editToggle = document.getElementById("sessionResultsEditToggle"); if (editToggle) { const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); const showEdit = typeVal === "sessionresults" && (sessionKeyLower === "race" || sessionKeyLower === "sprintrace"); editToggle.classList.toggle("d-none", !showEdit); } const compactToggle = document.getElementById("sessionResultsCompactToggle"); if (compactToggle) { compactToggle.classList.toggle("d-none", typeVal !== "sessionresults"); } const exportBtn = document.getElementById("sessionResultsExportRltoolsButton"); if (exportBtn) { exportBtn.classList.toggle("d-none", typeVal !== "sessionresults"); } const driversTeamsPills = document.querySelector("#season_viewer .drivers-teams-pills"); if (driversTeamsPills) { driversTeamsPills.classList.toggle("d-none", hideDriversTeamsPills); } const standingsDetailsButton = document.getElementById("standingsDetailsButton"); if (standingsDetailsButton) { standingsDetailsButton.classList.toggle("d-none", !showStandingsOnlyControls); } const tableTypeWrapper = document.getElementById("tableTypeButton")?.closest(".dropdown-global"); if (tableTypeWrapper) { tableTypeWrapper.classList.toggle("d-none", !showStandingsOnlyControls); } const hideHistoric = document.querySelector(".hide-historic-drivers"); if (hideHistoric) { const showHideHistoric = currentFormula === 1 && !showStandingsOnlyControls && typeVal !== "seasonreview" && typeVal !== "sessionresults" && driverOrTeams !== "teams"; hideHistoric.classList.toggle("d-none", !showHideHistoric); } updateAllTimeVisibilityForTeamsRecords(); } function manage_show_tables() { const recordsList = document.querySelector(".records-list") recordsList.innerHTML = "" recordsList.classList.add("d-none") const sessionResultsTable = document.querySelector(".session-results-table") if (sessionResultsTable) sessionResultsTable.classList.add("d-none") const seasonReviewBento = document.querySelector(".season-review-bento") seasonReviewBento.classList.add("d-none") if (isYearSelected) { if (driverOrTeams === "drivers") { document.querySelector(".teams-table").classList.add("d-none") document.querySelector(".drivers-table").classList.remove("d-none") } else { document.querySelector(".teams-table").classList.remove("d-none") document.querySelector(".drivers-table").classList.add("d-none") } } else { document.querySelector(".teams-table").classList.add("d-none") document.querySelector(".drivers-table").classList.add("d-none") } } document.querySelectorAll("#tableTypeDropdown a").forEach(function (elem) { elem.addEventListener("click", function () { pointsOrPos = elem.dataset.value //count time to execute the function let start = performance.now() change_points_pos_drivers() let end = performance.now() start = performance.now() change_points_pos_teams() end = performance.now() document.querySelector("#tableTypeButton span").textContent = elem.textContent syncTableTypeDropdownChecks() }) }) function forceStandingsCurrentSeason() { const recordsButton = document.getElementById("recordsTypeButton") const standingsItem = document.querySelector("#recordsTypeDropdown a[data-value='standings']") if (recordsButton) { recordsButton.dataset.value = "standings" const label = recordsButton.querySelector("span.dropdown-label") if (label) { label.textContent = standingsItem ? standingsItem.textContent : "Standings" } } syncRecordsTypeDropdownChecks() updateTopPanelControlsVisibility(); const yearMenu = document.querySelector("#yearMenu") const yearItems = yearMenu ? Array.from(yearMenu.querySelectorAll("a")) : [] if (yearItems.length > 1) { const currentYearEl = yearItems.find(item => item.dataset.year !== "all") if (currentYearEl) { manageRecordsSelected(currentYearEl) } } } function updateSeriesControls() { const showRecordsControls = currentFormula === 1 const recordsWrapper = document.getElementById("recordsTypeButton")?.closest(".dropdown-global") const yearWrapper = document.getElementById("yearButton")?.closest(".dropdown-global") if (recordsWrapper) { recordsWrapper.classList.toggle("d-none", !showRecordsControls) } if (yearWrapper) { yearWrapper.classList.toggle("d-none", !showRecordsControls) } } document.querySelectorAll("#seriesTypeDropdown a").forEach(function (elem) { elem.addEventListener("click", function () { const value = parseInt(elem.dataset.value, 10) currentFormula = value const seriesButton = document.getElementById("seriesTypeButton") seriesButton.querySelector("span.dropdown-label").textContent = elem.textContent seriesButton.dataset.value = elem.dataset.value syncSeriesTypeDropdownChecks() updateSeriesControls() if (currentFormula !== 1) { forceStandingsCurrentSeason() } else if (document.querySelector("#recordsTypeButton").dataset.value === "standings") { manageRecordsSelected(null) } }) }) function change_points_pos_drivers() { driverCells.forEach(function (cell) { if (cell.dataset[pointsOrPos] !== undefined) { cell.innerText = cell.dataset[pointsOrPos] } else { cell.innerText = "-" } }) } function renderTeamCellList(cell, values) { cell.innerHTML = "" if (!Array.isArray(values) || values.length === 0) { cell.innerText = "-" return } const container = document.createElement("div") container.className = "teams-table-multi" values.forEach((value) => { const item = document.createElement("div") item.className = "teams-table-multi-item" item.textContent = value container.appendChild(item) }) cell.appendChild(container) } function change_points_pos_teams() { teamCells.forEach(function (cell) { if (currentFormula === 3 && (pointsOrPos === "pos" || pointsOrPos === "quali")) { const listKey = pointsOrPos === "pos" ? "poslist" : "qualilist" if (cell.dataset[listKey]) { try { const values = JSON.parse(cell.dataset[listKey]) renderTeamCellList(cell, values) return } catch (err) { // fall back to text if data is malformed } } } if (cell.dataset[pointsOrPos] !== undefined) { cell.innerText = cell.dataset[pointsOrPos] } else { cell.innerText = "-" } }) } function getTeamAbbr(teamId) { const abbr = team_dict[teamId] return abbr ? abbr.toUpperCase() : "" } function buildTeamAbbrElement(teamId, sizeClass) { const abbr = getTeamAbbr(teamId) const abbrDiv = document.createElement("div") abbrDiv.classList = "team-logo-abbr" if (sizeClass) { abbrDiv.classList.add(sizeClass) } //get team logo from logos_disc if exists let logo_src = logos_disc[teamId] if (logo_src) { let logo = document.createElement("img") logo.classList = sizeClass logo.dataset.teamid = teamId logo.setAttribute("src", logo_src) abbrDiv.appendChild(logo) } else{ abbrDiv.textContent = abbr } return abbrDiv } function createHeaderCell(trackId, labelSuffix = "", baseClass = "drivers-table-normal") { let headerPos = document.createElement("div") headerPos.className = `${baseClass} bold-font flag-header` let headerPosFlag = document.createElement("img") let race = races_map[trackId] let flag_src = codes_dict[race] headerPosFlag.src = flag_src let headerPosDiv = document.createElement("div") headerPosDiv.classList.add("text-in-front") headerPosDiv.classList.add("bold-font") headerPosDiv.innerText = labelSuffix ? `${labelSuffix}` : races_names[trackId] if (labelSuffix === "SPR") { headerPosDiv.classList.add("sprint-label") headerPosDiv.classList.add("sprint-result-cell") } headerPos.appendChild(headerPosFlag) headerPos.appendChild(headerPosDiv) return headerPos } function formatDriverCellValue(value, type) { if (value === null || value === undefined) { return "-" } return manage_dataset_info_driver(value, undefined, type) } function syncFormulaFromCalendar(formula) { const seriesButton = document.getElementById("seriesTypeButton") if (seriesButton) { const label = formula === 2 ? "F2" : (formula === 3 ? "F3" : "F1") seriesButton.querySelector("span.dropdown-label").textContent = label seriesButton.dataset.value = String(formula) } syncSeriesTypeDropdownChecks() updateSeriesControls() } export function new_drivers_table(data) { calendarData = data syncFormulaFromCalendar(currentFormula) races_ids = [] let header = document.querySelector(".drivers-table-header") header.innerHTML = "" let driverDiv = document.createElement("div") driverDiv.classList = "drivers-table-driver bold-font" driverDiv.innerText = "DRIVER" let PositionDiv = document.createElement("div") PositionDiv.classList = "drivers-table-position bold-font" PositionDiv.innerText = "#" header.appendChild(PositionDiv) let posChangeHeader = document.createElement("div") posChangeHeader.classList = "standings-pos-change bold-font" posChangeHeader.innerText = "" header.appendChild(posChangeHeader) header.appendChild(driverDiv) const isF1 = currentFormula === 1 const driversData = document.querySelector(".drivers-table-data") driversData.className = "drivers-table-data" if (currentFormula === 2) { driversData.classList.add("f2-table-data") } else if (currentFormula === 3) { driversData.classList.add("f3-table-data") } data.forEach(function (elem) { const raceId = elem[0] const trackId = elem[1] races_ids.push(raceId) if (isF1) { header.appendChild(createHeaderCell(trackId)) } else { header.appendChild(createHeaderCell(trackId, "SPR")) header.appendChild(createHeaderCell(trackId)) } }) let GapDiv = document.createElement("div") GapDiv.classList = "standings-points-gap bold-font" GapDiv.innerText = "" header.appendChild(GapDiv) let PointsDiv = document.createElement("div") PointsDiv.classList = "drivers-table-points bold-font" PointsDiv.innerText = "PTS" header.appendChild(PointsDiv) } export function new_teams_table(data) { calendarData = data races_ids = [] let header = document.querySelector(".teams-table-header") header.innerHTML = "" let driverDiv = document.createElement("div") driverDiv.classList = "teams-table-team bold-font" driverDiv.innerText = "TEAM" let PositionDiv = document.createElement("div") PositionDiv.classList = "teams-table-position bold-font" PositionDiv.innerText = "#" header.appendChild(PositionDiv) let posChangeHeader = document.createElement("div") posChangeHeader.classList = "standings-pos-change bold-font" posChangeHeader.innerText = "" header.appendChild(posChangeHeader) header.appendChild(driverDiv) const isF1 = currentFormula === 1 const teamsData = document.querySelector(".teams-table-data") teamsData.className = "teams-table-data" if (currentFormula === 2) { teamsData.classList.add("f2-table-data") } else if (currentFormula === 3) { teamsData.classList.add("f3-table-data") } data.forEach(function (elem) { const raceId = elem[0] const trackId = elem[1] races_ids.push(raceId) if (isF1) { header.appendChild(createHeaderCell(trackId, "", "teams-table-normal")) } else { header.appendChild(createHeaderCell(trackId, "SPR", "teams-table-normal")) header.appendChild(createHeaderCell(trackId, "", "teams-table-normal")) } }) let GapDiv = document.createElement("div") GapDiv.classList = "standings-points-gap bold-font" GapDiv.innerText = "" header.appendChild(GapDiv) let PointsDiv = document.createElement("div") PointsDiv.classList = "teams-table-points bold-font" PointsDiv.innerText = "PTS" header.appendChild(PointsDiv) } function checkscroll() { let datazone = document.querySelector(".drivers-table-data") let pointscol = document.querySelector(".drivers-table-header").querySelector(".drivers-table-points") if (datazone.scrollHeight > datazone.clientHeight) { pointscol.style.width = "84px" } else { pointscol.style.width = "80px" } } function new_color_drivers_table() { let datazone = document.querySelector(".drivers-table-data"); let rows = datazone.querySelectorAll(".drivers-table-row"); rows.forEach(function (row) { let cells = row.querySelectorAll(".drivers-table-normal"); cells.forEach(function (cell) { let pos = cell.dataset.pos; if (pos) { let match = pos.match(/^(\d)(?:\s*\(.*\))?$/); if (match) { let number = match[1]; if (number === "1") { cell.classList.add("first"); } else if (number === "2") { cell.classList.add("second"); } else if (number === "3") { cell.classList.add("third"); } } } if (cell.dataset.fastlap === "1") { cell.classList.add("fastest"); } if (cell.dataset.dotd === "true") { cell.classList.add("dotd"); } if (cell.dataset.quali === "1") { cell.style.fontFamily = "Formula1Bold"; } }); }); } function manage_teams_table_logos() { let logos = document.querySelectorAll(".teams-table-logo-inner") logos.forEach(function (logo) { if (logo.dataset.teamid === "1") { logo.className = "teams-table-logo-inner ferrari-team-table-logo" } else if (logo.dataset.teamid === "2") { if (logo.tagName === "IMG") { const newElem = document.createElement("div"); newElem.className = "teams-table-logo-inner mclaren-team-table-logo"; newElem.dataset.teamid = logo.dataset.teamid; logo.replaceWith(newElem); logo = newElem; } else { logo.className = "teams-table-logo-inner mclaren-team-table-logo"; } } else if (logo.dataset.teamid === "3") { if (redbullReplace === "redbull") { logo.className = "teams-table-logo-inner redbull-team-table-logo" } else if (redbullReplace === "ford") { logo.className = "teams-table-logo-inner ford-team-table-logo" } logo.src = logos_disc[3] } else if (logo.dataset.teamid === "4") { logo.className = "teams-table-logo-inner merc-team-table-logo" } else if (logo.dataset.teamid === "5") { if (alpineReplace === "alpine") { logo.className = "teams-table-logo-inner alpine-team-table-logo" } else if (alpineReplace === "andretti") { logo.className = "teams-table-logo-inner ferrari-team-table-logo" logo.src = "../assets/images/logos/andretti2.png" } else if (alpineReplace === "renault") { logo.className = "teams-table-logo-inner ferrari-team-table-logo" logo.src = "../assets/images/logos/renault2.png" } else if (alpineReplace === "cadillac") { logo.className = "teams-table-logo-inner cadillac-team-table-logo" logo.src = logos_disc[5] } else if (alpineReplace === "lotus") { logo.src = "../assets/images/logos/lotus2.png" } } else if (logo.dataset.teamid === "6") { if (williamsReplace === "williams") { logo.className = "teams-table-logo-inner williams-team-table-logo" } else if (williamsReplace === "bmw") { logo.className = "teams-table-logo-inner bmw-team-table-logo" } logo.src = logos_disc[6] } else if (logo.dataset.teamid === "7") { if (haasReplace === "toyota") { if (logo.tagName === "IMG") { const newElem = document.createElement("div"); newElem.className = "teams-table-logo-inner toyota-team-table-logo"; newElem.dataset.teamid = logo.dataset.teamid; logo.replaceWith(newElem); logo = newElem; } else { logo.className = "teams-table-logo-inner toyota-team-table-logo"; } } else { if (logo.tagName !== "IMG") { const newElem = document.createElement("img"); newElem.className = "teams-table-logo-inner haas-team-table-logo"; newElem.dataset.teamid = logo.dataset.teamid; newElem.src = logos_disc[7]; logo.replaceWith(newElem); logo = newElem; } else { logo.className = "teams-table-logo-inner haas-team-table-logo"; logo.src = logos_disc[7]; } } } else if (logo.dataset.teamid === "8") { if (alphaReplace === "alphatauri") { logo.className = "teams-table-logo-inner alphatauri-team-table-logo" } else if (alphaReplace === "visarb") { logo.className = "teams-table-logo-inner merc-team-table-logo" } else if (alphaReplace === "hugo") { logo.className = "teams-table-logo-inner hugo-team-table-logo" } else if (alphaReplace === "toyota") { if (logo.tagName === "IMG") { const newElem = document.createElement("div"); newElem.className = "teams-table-logo-inner toyota-team-table-logo"; newElem.dataset.teamid = logo.dataset.teamid; logo.replaceWith(newElem); logo = newElem; } else { logo.className = "teams-table-logo-inner toyota-team-table-logo"; } } else if (alphaReplace === "porsche") { logo.className = "teams-table-logo-inner porsche-team-table-logo" } else if (alphaReplace === "brawn") { logo.className = "teams-table-logo-inner brawn-team-table-logo" logo.src = "../assets/images/logos/brawn2.png" } if (alphaReplace !== "toyota" && logo.tagName !== "IMG") { const newElem = document.createElement("img"); newElem.className = logo.className; newElem.dataset.teamid = logo.dataset.teamid; newElem.src = logos_disc[8]; logo.replaceWith(newElem); logo = newElem; } if (alphaReplace !== "brawn" && logo.tagName === "IMG") { logo.src = logos_disc[8]; } } else if (logo.dataset.teamid === "9") { if (alfaReplace === "alfa") { logo.className = "teams-table-logo-inner merc-team-table-logo" } else if (alfaReplace === "audi") { logo.className = "teams-table-logo-inner audi-team-table-logo" } else if (alfaReplace === "stake") { logo.className = "teams-table-logo-inner stake-team-table-logo" } else if (alfaReplace === "sauber") { logo.className = "teams-table-logo-inner ferrari-team-table-logo" } } else if (logo.dataset.teamid === "10") { if (astonReplace === "aston") { logo.className = "teams-table-logo-inner aston-team-table-logo" } else if (astonReplace === "racingpoint") { logo.className = "teams-table-logo-inner racingpoint-team-table-logo" } else if (astonReplace === "jordan") { logo.className = "teams-table-logo-inner jordan-team-table-logo" } logo.src = logos_disc[10] } else if (logo.dataset.teamid === "32") { logo.className = "teams-table-logo-inner custom-team-table-logo custom-replace" } }) } function manage_teams_table_names() { let names = document.querySelectorAll(".teams-table-team") names.forEach(function (name) { if (name.dataset.teamid === "5") { if (alpineReplace === "alpine") { name.firstChild.innerText = "ALPINE" } else if (alpineReplace === "andretti") { name.firstChild.innerText = "ANDRETTI" } else if (alpineReplace === "renault") { name.firstChild.innerText = "RENAULT" } else if (alpineReplace === "cadillac") { name.firstChild.innerText = "CADILLAC" } else if (alpineReplace === "lotus") { name.firstChild.innerText = "LOTUS" } } else if (name.dataset.teamid === "8") { if (alphaReplace === "alphatauri") { name.firstChild.innerText = "ALPHA TAURI" } else if (alphaReplace === "visarb") { name.firstChild.innerText = "VISA CASHAPP RB" } else if (alphaReplace === "hugo") { name.firstChild.innerText = "HUGO" } else if (alphaReplace === "toyota") { name.firstChild.innerText = "TOYOTA" } else if (alphaReplace === "porsche") { name.firstChild.innerText = "PORSCHE" } else if (alphaReplace === "brawn") { name.firstChild.innerText = "BRAWN GP" } } else if (name.dataset.teamid === "9") { if (alfaReplace === "alfa") { name.firstChild.innerText = "ALFA ROMEO" } else if (alfaReplace === "audi") { name.firstChild.innerText = "AUDI" } else if (alfaReplace === "stake") { name.firstChild.innerText = "STAKE SAUBER" } else if (alfaReplace === "sauber") { name.firstChild.innerText = "SAUBER" } } else if (name.dataset.teamid === "3") { if (redbullReplace === "redbull") { name.firstChild.innerText = "RED BULL" } else if (redbullReplace === "ford") { name.firstChild.innerText = "FORD" } } else if (name.dataset.teamid === "10") { if (astonReplace === "aston") { name.firstChild.innerText = "ASTON MARTIN" } else if (astonReplace === "racingpoint") { name.firstChild.innerText = "RACING POINT" } else if (astonReplace === "jordan") { name.firstChild.innerText = "JORDAN" } } }) } function new_color_teams_table() { let datazone = document.querySelector(".teams-table-data") calendarData.forEach(function (race) { let id = race[0] let colCells = datazone.querySelectorAll(".teams-table-normal[data-raceid='" + id + "']"); if (colCells.length > 0) { let values = []; colCells.forEach(function (cell, index) { let value = cell.dataset.points; values.push([value, index]); if (cell.dataset.quali1 === "1" || cell.dataset.quali2 === "1") { cell.style.fontFamily = "Formula1Bold" } if (cell.dataset.fastlap1 === "1" || cell.dataset.fastlap2 === "1") { cell.classList.add("fastest") } }); const hasResults = values.some(([value], idx) => { const text = colCells[idx].textContent?.trim(); return value !== undefined && value !== null && String(value) !== "" && text !== "-"; }); if (!hasResults) { return; } values.sort((a, b) => { function parseValue(val) { if (val === null || val === undefined || val === "") return 0; if (typeof val === "number") return val; const match = String(val).match(/^(\d+)(?:\((\d+)\))?$/); if (match) { const base = parseInt(match[1], 10); const extra = match[2] ? parseInt(match[2], 10) : 0; return base + extra; } return 0; } const totalA = parseValue(a[0]); const totalB = parseValue(b[0]); return totalB - totalA; }); let topThree = values.slice(0, 3); colCells[topThree[0][1]].classList.add("first"); colCells[topThree[1][1]].classList.add("second"); colCells[topThree[2][1]].classList.add("third"); } }) } function order_teams_table() { let datazone = document.querySelector(".teams-table-data") let rows = datazone.querySelectorAll(".teams-table-row") let ordered = Array.from(rows).sort((a, b) => parseInt(a.querySelector(".teams-table-position").innerText) - parseInt(b.querySelector(".teams-table-position").innerText)) datazone.innerHTML = "" ordered.forEach(function (row, index) { let odd = index % 2 === 0 if (odd) { row.classList.add("odd") } datazone.appendChild(row) }) } export function new_load_drivers_table(data) { seasonResults = data let datazone = document.querySelector(".drivers-table-data") let pointsInfo = data[2] datazone.innerHTML = "" data = data[0] data = new_order_drivers(data) let driver1Poitns = 0, driver2Points = 0; const driverRows = []; data.forEach(function (driver, index) { let odd = index % 2 === 0 let races_done = driver["races"].map(x => x.raceId) let result = new_addDriver(driver, races_done, odd) driverRows.push(result) const points = result.points if (index === 0) { driver1Poitns = points } else if (index === 1) { driver2Points = points } }) const leaderPoints = driverRows[0]?.points ?? 0; updateStandingsPointsGaps(driverRows, leaderPoints); if (currentFormula === 1) { checkIfDriverIsChampion(data[0], driver1Poitns, driver2Points, pointsInfo, driverRows) } else { const firstDriverPos = document.querySelector(".drivers-table-data .drivers-table-position") const firstDriverPoints = document.querySelector(".drivers-table-data .drivers-table-points") if (firstDriverPos) firstDriverPos.classList.remove("champion") if (firstDriverPoints) firstDriverPoints.classList.remove("champion") } checkscroll() new_color_drivers_table() driverCells = document.querySelectorAll(".drivers-table-data .drivers-table-normal") } function checkIfDriverIsChampion(driver1, driver1Points, driver2Points, pointsInfo, driverRows = []) { if (driver1 !== undefined) { const lastRaceDone = driver1["races"][driver1["races"].length - 1]["raceId"]; const lastRaceIndex = calendarData.findIndex(x => x[0] === lastRaceDone); racesLeftCount = calendarData.length - (lastRaceIndex + 1); sprintsLeft = calendarData.filter(x => x[2] === 1 && x[0] >= lastRaceDone).length const maxRacePoints = Number(pointsInfo?.twoBiggestPoints?.[0]?.[0] ?? pointsInfo?.twoBiggestPoints?.[0] ?? 0); const isDoublePoints = Number(pointsInfo?.isLastRaceDouble) === 1; const fastestLapBonus = Number(pointsInfo?.fastestLapBonusPoint) === 1; const poleBonus = Number(pointsInfo?.poleBonusPoint) === 1; const pointsDif = driver1Points - driver2Points let pointsRemaining = racesLeftCount * maxRacePoints + sprintsLeft * 8 + (isDoublePoints ? maxRacePoints : 0) + (fastestLapBonus ? racesLeftCount : 0) + (poleBonus ? racesLeftCount : 0) const firstDriverPos = document.querySelector(".drivers-table-data .drivers-table-position") const firstDriverPoints = document.querySelector(".drivers-table-data .drivers-table-points") const championClinched = pointsDif > pointsRemaining; if (championClinched) { firstDriverPos.classList.add("champion") firstDriverPoints.classList.add("champion") } else { firstDriverPos.classList.remove("champion") firstDriverPoints.classList.remove("champion") } driverRows.forEach((row) => { row?.row?.classList.remove("last-title-contender"); }); driverRows.forEach((row, index) => { if (!row?.pointsDiv) return; if (index === 0) { row.pointsDiv.classList.remove("eliminated"); return; } const eliminated = (Number(row.points) + Number(pointsRemaining)) < Number(driver1Points); row.pointsDiv.classList.toggle("eliminated", eliminated); }); if (!championClinched) { for (let i = driverRows.length - 1; i >= 0; i--) { const row = driverRows[i]; if (!row?.row) continue; const hasChance = (Number(row.points) + Number(pointsRemaining)) >= Number(driver1Points); if (hasChance) { row.row.classList.add("last-title-contender"); break; } } } } } function new_order_drivers(array) { return array.sort((a, b) => a["championshipPosition"] - b["championshipPosition"]); } export function update_logo(team, logo, newTeam) { if (team === "alpine") { alpineReplace = newTeam logos_disc[5] = logo } else if (team === "williams") { williamsReplace = newTeam logos_disc[6] = logo } else if (team === "haas") { haasReplace = newTeam || "haas" logos_disc[7] = logo } else if (team === "alphatauri") { alphaReplace = newTeam logos_disc[8] = logo } else if (team === "alfa") { alfaReplace = newTeam logos_disc[9] = logo } else if (team === "redbull") { redbullReplace = newTeam logos_disc[3] = logo } else if (team === "aston") { astonReplace = newTeam logos_disc[10] = logo } } export function reloadTables() { let datazone = document.querySelector(".drivers-table-data") //if not empty if (datazone.innerHTML !== "") { new_drivers_table(calendarData) new_load_drivers_table(seasonResults) new_teams_table(calendarData) new_load_teams_table(seasonResults) } } export function new_load_teams_table(data) { // Mantenemos el mismo "shape" de entrada: // data = [driversArray, pairTeamPos, pointsInfo] const pairTeamPos = data[data.length - 2]; const pointsInfo = data[data.length - 1]; // Mapa posEquipo -> posición (1..10) const pairTeamPosDict = {}; pairTeamPos.forEach(function (pair) { pairTeamPosDict[pair[0]] = { pos: Number(pair[1]), lastPositionChange: Number(pair[2] ?? 0) }; }); // Ahora data[0] es el array de pilotos con formato-objeto const drivers = data[0]; const datazone = document.querySelector(".teams-table-data"); datazone.innerHTML = ""; // Estructura: teamData[teamId] = Map let teamIds = currentFormula === 1 ? f1_teams : (currentFormula === 2 ? f2_teams : f3_teams) const teamData = {}; teamIds.forEach((id) => { teamData[id] = new Map(); }); if (currentFormula === 1 && game_version === 2024 && custom_team) { teamData[32] = new Map(); } else{ delete teamData[32]; //remove 32 from teamIds teamIds = teamIds.filter(id => id !== 32); } // Construimos el map por equipo/carrera drivers.forEach(function (driver) { // driver.latestTeamId sigue existiendo, pero para cada carrera usamos race.teamId driver.races?.forEach(function (raceObj) { const team = raceObj.teamId; if (!teamData[team]) return; const bucket = teamData[team]; const arr = bucket.get(raceObj.raceId) || []; arr.push(raceObj); bucket.set(raceObj.raceId, arr); }); }); // Normalizamos: aseguramos que cada equipo tenga entradas (vacías) para todas las carreras for (let team in teamData) { const bucket = teamData[team]; races_ids.forEach(rid => { if (!bucket.has(rid)) bucket.set(rid, []); // aún no llegaron los 2 pilotos o no corrieron }); } // Pintamos filas por equipo, usando tu orden/posiciones let team1Points = 0, team2Points = 0, firstTeamId = 0; const teamRows = []; teamIds.forEach((teamId) => { const teamInfo = pairTeamPosDict[teamId] || {}; const pos = teamInfo.pos; const lastPositionChange = teamInfo.lastPositionChange; let teamName = combined_dict[teamId] //remove the final (F2) or (F3) that may exist if (teamName && (teamName.endsWith(" (F2)") || teamName.endsWith(" (F3)"))) { teamName = teamName.slice(0, -5) } teamName = formatTeamNameForDisplay(teamName); const result = new_addTeam(teamData[teamId], teamName, pos, teamId, lastPositionChange); const points = result.points; teamRows.push({ teamId, pos, points, row: result.row, posDiv: result.posDiv, pointsDiv: result.pointsDiv, pointsGapDiv: result.pointsGapDiv }); if (pos === 1) { team1Points = points; firstTeamId = teamId; } else if (pos === 2) { team2Points = points; } }); const needsFallbackPositions = false; const sortByPoints = currentFormula !== 1 || needsFallbackPositions; if (sortByPoints) { const sorted = [...teamRows].sort((a, b) => b.points - a.points); sorted.forEach((team, index) => { const position = index + 1; team.pos = position; if (team.posDiv) { team.posDiv.innerText = String(position); team.posDiv.dataset.position = String(position); } }); } const leaderTeamPoints = teamRows.find(team => Number(team.pos) === 1)?.points ?? 0; updateStandingsPointsGaps(teamRows, leaderTeamPoints); new_color_teams_table(); if (currentFormula === 1) { checkIfTeamIsChamp(team1Points, team2Points, pointsInfo, teamRows); manage_teams_table_logos(); manage_teams_table_names(); } else { const firstTeamPos = document.querySelector(".teams-table-data .teams-table-position") const firstTeamPoints = document.querySelector(".teams-table-data .teams-table-points") if (firstTeamPos) firstTeamPos.classList.remove("champion") if (firstTeamPoints) firstTeamPoints.classList.remove("champion") } order_teams_table(); teamCells = document.querySelectorAll(".teams-table-data .teams-table-normal"); } function checkIfTeamIsChamp(team1Points, team2Points, pointsInfo, teamRows = []) { const sortedTeams = [...teamRows] .filter(team => team) .sort((a, b) => Number(a.pos) - Number(b.pos)); const leaderTeam = sortedTeams.find(team => Number(team.pos) === 1) || sortedTeams[0]; const runnerUpTeam = sortedTeams.find(team => Number(team.pos) === 2) || sortedTeams[1]; const leaderPoints = Number(leaderTeam?.points ?? team1Points) || 0; const runnerUpPoints = Number(runnerUpTeam?.points ?? team2Points) || 0; const pointsDif = leaderPoints - runnerUpPoints const maxFirstPoints = Number(pointsInfo?.twoBiggestPoints?.[0]?.[0] ?? pointsInfo?.twoBiggestPoints?.[0] ?? 0); const maxSecondPoints = Number(pointsInfo?.twoBiggestPoints?.[1]?.[0] ?? pointsInfo?.twoBiggestPoints?.[1] ?? 0); const maxTeamRacePoints = maxFirstPoints + maxSecondPoints; const isDoublePoints = Number(pointsInfo?.isLastRaceDouble) === 1; const fastestLapBonus = Number(pointsInfo?.fastestLapBonusPoint) === 1; const poleBonus = Number(pointsInfo?.poleBonusPoint) === 1; let pointsRemaining = racesLeftCount * maxTeamRacePoints + sprintsLeft * 15 + (isDoublePoints ? maxTeamRacePoints : 0) + (fastestLapBonus ? racesLeftCount : 0) + (poleBonus ? racesLeftCount : 0) const firstTeamRow = document.querySelector( ".teams-table-position[data-position='1']" )?.closest(".teams-table-row"); const firstTeamPos = firstTeamRow?.querySelector(".teams-table-position"); const firstTeamPoints = firstTeamRow?.querySelector(".teams-table-points"); const championClinched = pointsDif > pointsRemaining; if (championClinched) { firstTeamPos.classList.add("champion") firstTeamPoints.classList.add("champion") } else { firstTeamPos.classList.remove("champion") firstTeamPoints.classList.remove("champion") } teamRows.forEach((team) => { team?.row?.classList.remove("last-title-contender"); }); teamRows.forEach((team) => { if (!team?.pointsDiv) return; if (Number(team.pos) === 1) { team.pointsDiv.classList.remove("eliminated"); return; } const eliminated = (Number(team.points) + Number(pointsRemaining)) < Number(leaderPoints); team.pointsDiv.classList.toggle("eliminated", eliminated); }); if (!championClinched) { for (let i = sortedTeams.length - 1; i >= 0; i--) { const team = sortedTeams[i]; if (!team?.row) continue; const hasChance = (Number(team.points) + Number(pointsRemaining)) >= Number(leaderPoints); if (hasChance) { team.row.classList.add("last-title-contender"); break; } } } } function new_addTeam(teamRaceMap, name, pos, id, lastPositionChange = 0) { // teamRaceMap: Map let data = document.querySelector(".teams-table-data"); let row = document.createElement("div"); row.classList = "teams-table-row"; let nameDiv = document.createElement("div"); let teamName = document.createElement("span"); nameDiv.dataset.teamid = id; nameDiv.classList = "teams-table-team bold-font"; teamName.innerText = name.toUpperCase(); nameDiv.appendChild(teamName); if (currentFormula === 1) { const engineId = engine_allocations?.[id] const engineNameText = engineId != null ? engine_names?.[engineId] : "" const engineDiv = document.createElement("div") engineDiv.className = "teams-table-engine" const engineLogo = document.createElement("img") engineLogo.className = "teams-table-engine-logo" engineLogo.src = getEngineLogoSrc(engineNameText) engineLogo.alt = "" engineLogo.setAttribute("aria-hidden", "true") const engineName = document.createElement("span") engineName.classList = "teams-table-engine-name bold-font" engineName.textContent = engineNameText // engineDiv.appendChild(engineLogo) disabled for now engineDiv.appendChild(engineName) nameDiv.appendChild(engineDiv) } row.appendChild(nameDiv); let posDiv = document.createElement("div"); posDiv.classList = "teams-table-position bold-font"; posDiv.dataset.position = pos; posDiv.innerText = pos; row.appendChild(posDiv); const posChangeDiv = document.createElement("div"); posChangeDiv.className = "standings-pos-change"; const posChangeNumber = document.createElement("span"); posChangeNumber.className = "standings-pos-change-number"; const posChangeIcon = document.createElement("i"); posChangeDiv.appendChild(posChangeNumber); posChangeDiv.appendChild(posChangeIcon); setStandingsPositionChange(posChangeDiv, lastPositionChange); row.appendChild(posChangeDiv); let logoDiv = document.createElement("div"); logoDiv.classList = "teams-table-logo"; logoDiv.classList.add(team_dict[id] + "iconback"); if (currentFormula === 1 && logos_disc[id]) { let logo = document.createElement("img"); logo.classList = "teams-table-logo-inner"; logo.dataset.teamid = id; logo.setAttribute("src", logos_disc[id]); logoDiv.appendChild(logo); } else { logoDiv.appendChild(buildTeamAbbrElement(id, "junior-team-logo-team")); } row.appendChild(logoDiv); row.appendChild(nameDiv); let teampoints = 0; const safePoints = (v) => { if (v === -1) return 0; // DNF → 0 puntos const n = Number.parseInt(String(v), 10); if (!Number.isFinite(n)) return 0; return Math.max(0, n); }; const pickTopEntries = (pair) => { if (!pair || pair.length <= 2) { return [pair[0] || null, pair[1] || null]; } const scoring = pair .map((entry, index) => ({ entry, index, total: safePoints(entry?.points) + safePoints(entry?.qualifyingPoints) + safePoints(entry?.sprintPoints) })) .sort((a, b) => b.total - a.total || a.index - b.index); return [scoring[0]?.entry || null, scoring[1]?.entry || null]; }; const formatTeamPosValue = (entry, useSprint) => { if (!entry) return "-" const points = useSprint ? entry.sprintPoints : entry.points const pos = useSprint ? entry.sprintPos : entry.finishingPos if (points === -1 || pos === -1) return "DNF" if (pos === null || pos === undefined) return "-" return String(pos) }; const formatTeamQualiValue = (entry, useSprint) => { if (!entry) return "-" if (useSprint) { return entry.sprintQualiPos !== undefined && entry.sprintQualiPos !== null ? String(entry.sprintQualiPos) : "-" } const quali = entry.qualifyingPos ?? 99 return String(quali) }; const buildTeamResultList = (entries, type, useSprint) => { if (!Array.isArray(entries) || entries.length === 0) return [] if (type === "pos") { return entries.map((entry) => formatTeamPosValue(entry, useSprint)) } if (type === "quali") { return entries.map((entry) => formatTeamQualiValue(entry, useSprint)) } return [] }; // Iteramos las carreras en orden por races_ids (como antes) races_ids.forEach((raceId) => { const pair = (teamRaceMap && teamRaceMap.get(raceId)) || []; const isF1 = currentFormula === 1; if (isF1) { const raceDiv = document.createElement("div"); raceDiv.classList = "teams-table-normal"; if (pair.length > 0) { // Aseguramos 2 elementos (puede faltar uno) y priorizamos los que más puntúan const [d1, d2] = pickTopEntries(pair); const d1Points = d1 ? (safePoints(d1.points) + safePoints(d1.qualifyingPoints)) : 0; const d2Points = d2 ? (safePoints(d2.points) + safePoints(d2.qualifyingPoints)) : 0; const d1Pos = d1 ? (d1.points === -1 || d1.finishingPos === -1 ? "DNF" : d1.finishingPos) : "-"; const d2Pos = d2 ? (d2.points === -1 || d2.finishingPos === -1 ? "DNF" : d2.finishingPos) : "-"; // datasets base raceDiv.dataset.raceid = raceId; raceDiv.dataset.pointsCount = d1Points + d2Points; const s1pts = d1?.sprintPoints; const s2pts = d2?.sprintPoints; const s1pos = d1?.sprintPos; const s2pos = d2?.sprintPos; raceDiv.dataset.points = manage_dataset_info_team( [d1Points, d2Points], (typeof s1pts !== "undefined" || typeof s2pts !== "undefined") ? [s1pts ?? 0, s2pts ?? 0] : undefined, "points" ); raceDiv.dataset.pos = manage_dataset_info_team( [d1Pos, d2Pos], (s1pos == null || s2pos == null) ? undefined : [s1pos, s2pos], "pos" ); raceDiv.dataset.quali = manage_dataset_info_team( [d1 ? d1.qualifyingPos ?? 99 : 99, d2 ? d2.qualifyingPos ?? 99 : 99], undefined, "quali" ); raceDiv.dataset.quali1 = d1 ? d1.qualifyingPos ?? 99 : 99; raceDiv.dataset.quali2 = d2 ? d2.qualifyingPos ?? 99 : 99; raceDiv.dataset.fastlap1 = d1 && d1.fastestLap ? 1 : 0; raceDiv.dataset.fastlap2 = d2 && d2.fastestLap ? 1 : 0; // Suma de puntos de carrera teampoints += parseInt(raceDiv.dataset.pointsCount); // Sprint let d1SprintPoints = 0, d2SprintPoints = 0; let d1SprintPos = "-", d2SprintPos = "-"; if (typeof s1pts !== "undefined") { if (s1pts === -1) { d1SprintPoints = 0; d1SprintPos = "DNF"; } else { d1SprintPoints = parseInt(s1pts) || 0; d1SprintPos = (typeof s1pos === "number" ? s1pos : (s1pos ?? "-")); } } if (typeof s2pts !== "undefined") { if (s2pts === -1) { d2SprintPoints = 0; d2SprintPos = "DNF"; } else { d2SprintPoints = parseInt(s2pts) || 0; d2SprintPos = (typeof s2pos === "number" ? s2pos : (s2pos ?? "-")); } } raceDiv.dataset.sprintpoints = d1SprintPoints + d2SprintPoints; raceDiv.dataset.sprintpos1 = d1SprintPos; raceDiv.dataset.sprintpos2 = d2SprintPos; teampoints += parseInt(raceDiv.dataset.sprintpoints); raceDiv.textContent = raceDiv.dataset[pointsOrPos]; } else { raceDiv.innerText = "-"; } row.appendChild(raceDiv); } else { const sprintDiv = document.createElement("div"); const featureDiv = document.createElement("div"); sprintDiv.classList = "teams-table-normal"; featureDiv.classList = "teams-table-normal"; sprintDiv.classList.add("sprint-result-cell"); featureDiv.classList.add("feature-result-cell"); sprintDiv.dataset.raceid = raceId; featureDiv.dataset.raceid = raceId; if (pair.length > 0) { const [d1, d2] = pickTopEntries(pair); const allEntries = pair; const wantsList = currentFormula === 3 && (pointsOrPos === "pos" || pointsOrPos === "quali"); const sprintPoints = [d1?.sprintPoints ?? null, d2?.sprintPoints ?? null]; const sprintPos = [d1?.sprintPos ?? null, d2?.sprintPos ?? null]; const hasSprint = sprintPos.some(v => v !== null && v !== undefined); const sprintQuali = [d1?.sprintQualiPos ?? "-", d2?.sprintQualiPos ?? "-"]; sprintDiv.dataset.quali = manage_dataset_info_team( sprintQuali, undefined, "quali" ); if (currentFormula === 3) { const sprintPosList = hasSprint ? buildTeamResultList(allEntries, "pos", true) : []; const sprintQualiList = buildTeamResultList(allEntries, "quali", true); sprintDiv.dataset.poslist = JSON.stringify(sprintPosList); sprintDiv.dataset.qualilist = JSON.stringify(sprintQualiList); if (pointsOrPos === "pos") { renderTeamCellList(sprintDiv, sprintPosList); } else if (pointsOrPos === "quali") { renderTeamCellList(sprintDiv, sprintQualiList); } } if (hasSprint) { if (currentFormula === 3) { const sprintPointsTotal = allEntries.reduce( (sum, entry) => sum + safePoints(entry?.sprintPoints), 0 ); sprintDiv.dataset.points = sprintPointsTotal === 0 ? "" : String(sprintPointsTotal); } else { sprintDiv.dataset.points = manage_dataset_info_team( [safePoints(sprintPoints[0]), safePoints(sprintPoints[1])], undefined, "points" ); } sprintDiv.dataset.pos = manage_dataset_info_team( [sprintPos[0] ?? "-", sprintPos[1] ?? "-"], undefined, "pos" ); if (!wantsList) { sprintDiv.textContent = sprintDiv.dataset[pointsOrPos]; } } else { sprintDiv.textContent = "-"; } const d1Points = d1 ? safePoints(d1.points) : 0; const d2Points = d2 ? safePoints(d2.points) : 0; const d1Pos = d1 ? (d1.points === -1 || d1.finishingPos === -1 ? "DNF" : d1.finishingPos) : "-"; const d2Pos = d2 ? (d2.points === -1 || d2.finishingPos === -1 ? "DNF" : d2.finishingPos) : "-"; const d1PointsTotal = safePoints(d1?.points) + safePoints(d1?.qualifyingPoints); const d2PointsTotal = safePoints(d2?.points) + safePoints(d2?.qualifyingPoints); if (currentFormula === 3) { const featurePointsTotal = allEntries.reduce( (sum, entry) => sum + safePoints(entry?.points) + safePoints(entry?.qualifyingPoints), 0 ); featureDiv.dataset.points = featurePointsTotal === 0 ? "" : String(featurePointsTotal); } else { featureDiv.dataset.points = manage_dataset_info_team( [d1PointsTotal, d2PointsTotal], undefined, "points" ); } featureDiv.dataset.pos = manage_dataset_info_team( [d1Pos, d2Pos], undefined, "pos" ); featureDiv.dataset.quali = manage_dataset_info_team( [d1 ? d1.qualifyingPos ?? 99 : "-", d2 ? d2.qualifyingPos ?? 99 : "-"], undefined, "quali" ); if (currentFormula === 3) { const featurePosList = buildTeamResultList(allEntries, "pos", false); const featureQualiList = buildTeamResultList(allEntries, "quali", false); featureDiv.dataset.poslist = JSON.stringify(featurePosList); featureDiv.dataset.qualilist = JSON.stringify(featureQualiList); if (pointsOrPos === "pos") { renderTeamCellList(featureDiv, featurePosList); } else if (pointsOrPos === "quali") { renderTeamCellList(featureDiv, featureQualiList); } } if (!wantsList) { featureDiv.textContent = featureDiv.dataset[pointsOrPos]; } const sprintTeamPoints = hasSprint ? (currentFormula === 3 ? allEntries.reduce( (sum, entry) => sum + safePoints(entry?.sprintPoints), 0 ) : (safePoints(sprintPoints[0]) + safePoints(sprintPoints[1]))) : 0; const featureTeamPoints = currentFormula === 3 ? allEntries.reduce( (sum, entry) => sum + safePoints(entry?.points) + safePoints(entry?.qualifyingPoints), 0 ) : (d1Points + d2Points + safePoints(d1?.qualifyingPoints) + safePoints(d2?.qualifyingPoints)); teampoints += featureTeamPoints + sprintTeamPoints; } else { sprintDiv.textContent = "-"; featureDiv.textContent = "-"; } row.appendChild(sprintDiv); row.appendChild(featureDiv); } }); const pointsGapDiv = document.createElement("div"); pointsGapDiv.className = "standings-points-gap"; row.appendChild(pointsGapDiv); let pointsDiv = document.createElement("div"); pointsDiv.classList = "teams-table-points bold-font"; pointsDiv.innerText = (currentFormula === 3 && teampoints === 0) ? "" : teampoints; row.appendChild(pointsDiv); data.appendChild(row); return { points: teampoints, row, posDiv, pointsDiv, pointsGapDiv }; } function buildF1DriverLogoElement(teamId) { let logo = document.createElement("img"); logo.classList = "drivers-table-logo"; logo.dataset.teamid = teamId; if (teamId === 1) logo.classList.add("logo-ferrari-table"); if (teamId === 2) { logo = document.createElement("div"); logo.classList = "drivers-table-logo logo-mclaren-table logo-reduce"; logo.dataset.teamid = teamId; } if (teamId === 3) { logo.classList.add("logo-up-down-mid"); if (redbullReplace !== "redbull") { logo.classList.add(driversTableLogosDict[redbullReplace]); } } if (teamId === 6) { if (williamsReplace === "williams") { logo = document.createElement("div"); logo.classList.add("logo-williams-2026-table"); logo.dataset.teamid = teamId; } else { logo = document.createElement("img"); logo.classList = "drivers-table-logo logo-bmw-table"; logo.dataset.teamid = teamId; logo.src = logos_disc[6]; } } if (teamId === 7 && haasReplace === "toyota") { logo = document.createElement("div"); logo.classList = "drivers-table-logo logo-toyota-table"; logo.dataset.teamid = teamId; } if (teamId === 4) logo.classList.add("logo-merc-table"); if (teamId === 7 && haasReplace !== "toyota") logo.classList.add("logo-merc-table"); if (teamId === 5) { if (alpineReplace === "cadillac") { logo = document.createElement("img"); logo.classList = "drivers-table-logo"; logo.dataset.teamid = teamId; } else { logo = document.createElement("div"); logo.dataset.teamid = teamId; } logo.classList.add(driversTableLogosDict[alpineReplace]); } if (teamId === 8) { if (["alphatauri", "visarb", "brawn", "hugo"].includes(alphaReplace)) { logo = document.createElement("div"); logo.dataset.teamid = teamId; } else if (alphaReplace === "toyota") { logo = document.createElement("div"); logo.classList = "drivers-table-logo"; logo.dataset.teamid = teamId; } logo.classList.add(driversTableLogosDict[alphaReplace]); } if (teamId === 9) { if (alfaReplace === "sauber") { logo = document.createElement("div"); logo.classList = "drivers-table-logo"; logo.dataset.teamid = teamId; } logo.classList.add(driversTableLogosDict[alfaReplace]); } if (teamId === 10 || teamId === 32) { logo.classList.add("logo-up-down-little"); if (teamId === 10 && astonReplace !== "aston") { logo.classList.add(driversTableLogosDict[astonReplace]); } } if (teamId === 32) logo.classList.add("custom-replace"); if (logo.tagName === "IMG") logo.src = logos_disc[teamId]; return logo; } function buildDriverLogoDiv(teamId, opts = {}) { const { isF1 = currentFormula === 1, wrapperClass = "drivers-table-logo-div", juniorSizeClass = "junior-team-logo-driver", } = opts; const logoDiv = document.createElement("div"); logoDiv.className = wrapperClass; if (isF1) { const logo = buildF1DriverLogoElement(teamId); logoDiv.appendChild(logo); } else { logoDiv.appendChild(buildTeamAbbrElement(teamId, juniorSizeClass)); } if (team_dict[teamId]) { logoDiv.classList.add(team_dict[teamId] + "hoverback"); } return logoDiv; } function new_addDriver(driver, races_done, odd) { let data = document.querySelector(".drivers-table-data"); let row = document.createElement("div"); row.classList = "drivers-table-row"; if (odd) row.classList.add("odd"); const isF1 = currentFormula === 1; let nameDiv = document.createElement("div"); nameDiv.classList = "drivers-table-driver"; let name = driver["driverName"].split(" "); let nameContainer = document.createElement("div"); nameContainer.className = "name-container"; let spanName = document.createElement("span"); let spanLastName = document.createElement("span"); format_name(driver["driverName"], name, spanName, spanLastName); spanLastName.classList.add("bold-font"); spanLastName.dataset.teamid = driver["latestTeamId"]; row.dataset.teamid = driver["latestTeamId"]; nameContainer.appendChild(spanName); nameContainer.appendChild(spanLastName); nameDiv.appendChild(nameContainer); let posDiv = document.createElement("div"); posDiv.classList = "drivers-table-position bold-font"; posDiv.innerText = driver["championshipPosition"]; row.appendChild(posDiv); const posChangeDiv = document.createElement("div"); posChangeDiv.className = "standings-pos-change"; const posChangeNumber = document.createElement("span"); posChangeNumber.className = "standings-pos-change-number"; const posChangeIcon = document.createElement("i"); posChangeDiv.appendChild(posChangeNumber); posChangeDiv.appendChild(posChangeIcon); setStandingsPositionChange(posChangeDiv, driver["lastPositionChange"]); row.appendChild(posChangeDiv); const logoDiv = buildDriverLogoDiv(driver["latestTeamId"], { isF1 }); row.appendChild(logoDiv); row.appendChild(nameDiv); let driverpoints = 0; races_ids.forEach(function (raceid) { const race = driver.races?.find(r => r.raceId === raceid); if (isF1) { let raceDiv = document.createElement("div"); raceDiv.classList = "drivers-table-normal"; if (races_done.includes(raceid) && race) { const qualiPoints = parseInt(race.qualifyingPoints) || 0; const racePointsRaw = parseInt(race.points); const featurePoints = racePointsRaw === -1 ? -1 : racePointsRaw + Math.max(0, qualiPoints); const hasSprintPoints = typeof race.sprintPoints !== "undefined" && race.sprintPoints !== null; const hasSprintPos = typeof race.sprintPos !== "undefined" && race.sprintPos !== null; raceDiv.dataset.pos = manage_dataset_info_driver( race.finishingPos, hasSprintPos ? race.sprintPos : undefined, "pos" ); raceDiv.dataset.points = manage_dataset_info_driver( featurePoints, hasSprintPoints ? race.sprintPoints : undefined, "points" ); raceDiv.dataset.fastlap = race.fastestLap ? 1 : 0; // normaliza a 0/1 raceDiv.dataset.quali = manage_dataset_info_driver( race.qualifyingPos === 99 ? race.startingPos : race.qualifyingPos, undefined, "quali" ); raceDiv.dataset.gapToWinner = race.gapToWinner; raceDiv.dataset.gapToPole = race.gapToPole; raceDiv.dataset.dotd = race.driverOfTheDay //if its true or false // Sprint if (hasSprintPos) raceDiv.dataset.sprintpos = race.sprintPos; if (hasSprintPoints) { raceDiv.dataset.sprintpoints = race.sprintPoints; if (race.sprintPoints !== -1) { driverpoints += Math.max(0, parseInt(race.sprintPoints) || 0); } } // Puntos carrera (ignora -1) driverpoints += Math.max(0, parseInt(race.points) || 0); driverpoints += Math.max(0, parseInt(race.qualifyingPoints) || 0); raceDiv.textContent = raceDiv.dataset[pointsOrPos]; } else { raceDiv.innerText = "-"; } row.appendChild(raceDiv); } else { const sprintDiv = document.createElement("div"); const featureDiv = document.createElement("div"); sprintDiv.classList = "drivers-table-normal"; featureDiv.classList = "drivers-table-normal"; sprintDiv.classList.add("sprint-result-cell"); featureDiv.classList.add("feature-result-cell"); if (races_done.includes(raceid) && race) { const hasSprintPos = typeof race.sprintPos !== "undefined" && race.sprintPos !== null; const hasSprintPoints = typeof race.sprintPoints !== "undefined" && race.sprintPoints !== null; sprintDiv.dataset.quali = formatDriverCellValue(race.sprintQualiPos, "quali"); if (hasSprintPos) { sprintDiv.dataset.points = formatDriverCellValue(race.sprintPoints, "points"); sprintDiv.dataset.pos = formatDriverCellValue(race.sprintPos, "pos"); sprintDiv.dataset.gapToWinner = "-"; sprintDiv.dataset.gapToPole = "-"; sprintDiv.textContent = sprintDiv.dataset[pointsOrPos]; } else { sprintDiv.textContent = "-"; } featureDiv.dataset.pos = formatDriverCellValue(race.finishingPos, "pos"); const qualiPoints = parseInt(race.qualifyingPoints) || 0; const featurePoints = (parseInt(race.points) || 0) + Math.max(0, qualiPoints); featureDiv.dataset.points = formatDriverCellValue(featurePoints, "points"); featureDiv.dataset.fastlap = race.fastestLap ? 1 : 0; featureDiv.dataset.quali = formatDriverCellValue( race.qualifyingPos === 99 ? race.startingPos : race.qualifyingPos, "quali" ); featureDiv.dataset.gapToWinner = race.gapToWinner ?? "-"; featureDiv.dataset.gapToPole = race.gapToPole ?? "-"; featureDiv.dataset.dotd = race.driverOfTheDay; featureDiv.textContent = featureDiv.dataset[pointsOrPos]; driverpoints += Math.max(0, parseInt(race.points) || 0); driverpoints += Math.max(0, parseInt(race.qualifyingPoints) || 0); if (hasSprintPos && hasSprintPoints) { driverpoints += Math.max(0, parseInt(race.sprintPoints) || 0); } } else { sprintDiv.textContent = "-"; featureDiv.textContent = "-"; } row.appendChild(sprintDiv); row.appendChild(featureDiv); } }); const pointsGapDiv = document.createElement("div"); pointsGapDiv.className = "standings-points-gap"; row.appendChild(pointsGapDiv); let pointsDiv = document.createElement("div"); pointsDiv.classList = "drivers-table-points bold-font"; pointsDiv.innerText = driverpoints; row.appendChild(pointsDiv); data.appendChild(row); return { points: driverpoints, row, pointsDiv, pointsGapDiv }; } function manage_dataset_info_driver(info, sprintInfo, type) { let race, sprint; if (type === "points") { if (parseInt(info) === 0) { race = "" } else if (parseInt(info) === -1) { race = "DNF" } else { race = info } if (sprintInfo === undefined) { sprint = "" } else if (parseInt(sprintInfo) === 0 || parseInt(sprintInfo) === -1) { sprint = "" } else { sprint = sprintInfo } let res = `${race}${(sprint !== "") ? "(" + sprint + ")" : ""}` return res } else if (type === "pos") { if (parseInt(info) === -1) { race = "DNF" } else { race = info; } if (sprintInfo === undefined || parseInt(sprintInfo) > 8 || parseInt(sprintInfo) === -1) { sprint = "" } else { sprint = sprintInfo } let res = `${race}${(sprint !== "") ? "(" + sprint + ")" : ""}` return res } else if (type === "quali") { race = info; return race; } } function manage_dataset_info_team(info, sprintInfo, type) { let race, sprint; if (type === "points") { let d1Points = (info[0] !== -1 ? info[0] : 0) let d2Points = (info[1] !== -1 ? info[1] : 0) let combinedRace = parseInt(d1Points) + parseInt(d2Points) let combinedSprint; if (sprintInfo !== undefined) { combinedSprint = parseInt(sprintInfo[0]) + parseInt(sprintInfo[1]) } if (combinedRace === 0) { race = "" } else { race = combinedRace } if (sprintInfo === undefined || combinedSprint === 0) { sprint = "" } else if (combinedSprint !== 0) { sprint = combinedSprint } let res = `${race}${(sprint !== "") ? "(" + sprint + ")" : ""}` return res; } else if (type === "pos") { if (parseInt(info[0]) === -1) { info[0] = "DNF" } if (parseInt(info[1]) === -1) { info[1] = "DNF" } if (sprintInfo !== undefined) { if (parseInt(sprintInfo[0]) === -1 || parseInt(sprintInfo[0]) > 8) { sprintInfo[0] = "" } if (parseInt(sprintInfo[1]) === -1 || parseInt(sprintInfo[1]) > 8) { sprintInfo[1] = "" } } let res = `${info[0]}${(sprintInfo !== undefined && sprintInfo[0] !== "") ? "(" + sprintInfo[0] + ")" : ""}\n${info[1]}${(sprintInfo !== undefined && sprintInfo[1] !== "") ? "(" + sprintInfo[1] + ")" : ""}` return res; } else if (type === "quali") { let res = `${info[0]}\n${info[1]}` return res; } } function manageText(raceDiv) { if (raceDiv.innerText === "-") { return raceDiv } if (pointsOrPos === "points" || pointsOrPos === "pos") { let racePart = "" let sprintPart = "" if (raceDiv.dataset.points !== "-1") { if (pointsOrPos === "points") { racePart = raceDiv.dataset.points } else { racePart = raceDiv.dataset.pos } } else { racePart = "DNF" } if (raceDiv.dataset.points === "0" && pointsOrPos === "points") { racePart = "" } if (raceDiv.dataset.sprintpoints !== undefined) { if (raceDiv.dataset.sprintpoints !== "-1") { if (pointsOrPos === "points") { sprintPart = raceDiv.dataset.sprintpoints } else { sprintPart = raceDiv.dataset.sprintpos } } else { sprintPart = "DNF" } } if (raceDiv.dataset.sprintpoints === undefined || raceDiv.dataset.sprintpoints === "0") { raceDiv.innerText = racePart } else { raceDiv.innerText = racePart + "(" + sprintPart + ")" } } else if (pointsOrPos === "quali") { raceDiv.innerText = raceDiv.dataset.quali } else if (pointsOrPos === "gapWinner") { if (raceDiv.dataset.pos === "-1") { raceDiv.innerText = "DNF" } else { raceDiv.innerText = raceDiv.dataset.gapToWinner } } else if (pointsOrPos === "gapPole") { raceDiv.innerText = raceDiv.dataset.gapToPole } return raceDiv } function manageTeamsText(raceDiv) { if (raceDiv.innerText === "-") { return raceDiv } if (pointsOrPos === "points") { if (raceDiv.dataset.sprintpoints !== undefined) { let racePart = raceDiv.dataset.points let sprintPart = "(" + raceDiv.dataset.sprintpoints + ")" if (racePart === "0") { racePart = "" } if (sprintPart === "0") { sprintPart = "" } raceDiv.innerText = racePart + sprintPart } else { let racePart = raceDiv.dataset.points if (racePart === "0") { racePart = "" } raceDiv.innerText = racePart } } else if (pointsOrPos === "pos") { let d1Pos = "DNF" let d2Pos = "DNF" let d1SprPos = "" let d2SprPos = "" if (raceDiv.dataset.pos1 !== "DNF") { d1Pos = raceDiv.dataset.pos1 } if (raceDiv.dataset.pos2 !== "DNF") { d2Pos = raceDiv.dataset.pos2 } if (raceDiv.dataset.sprintpos1 !== undefined) { d1SprPos = raceDiv.dataset.sprintpos1 } if (raceDiv.dataset.sprintpos2 !== undefined) { d2SprPos = raceDiv.dataset.sprintpos2 } let text = d1Pos + "
" + d2Pos if (d1SprPos !== "" && d2SprPos !== "") { text = d1Pos + "(" + d1SprPos + ")" + "
" + d2Pos + "(" + d2SprPos + ")" } raceDiv.innerHTML = text } else if (pointsOrPos === "quali") { raceDiv.innerHTML = raceDiv.dataset.quali1 + "
" + raceDiv.dataset.quali2 } else if (pointsOrPos === "gapWinner") { let d1, d2; if (raceDiv.dataset.pos1 === "DNF") { d1 = "DNF" } else { d1 = raceDiv.dataset.gapToWinner1 } if (raceDiv.dataset.pos2 === "DNF") { d2 = "DNF" } else { d2 = raceDiv.dataset.gapToWinner2 } raceDiv.innerHTML = d1 + "
" + d2 } else if (pointsOrPos === "gapPole") { raceDiv.innerHTML = raceDiv.dataset.gapToPole1 + "
" + raceDiv.dataset.gapToPole2 } return raceDiv } /** * Creates the year selector menu * @param {String} actualYear current year of the save */ export function generateYearsMenu(actualYear) { document.querySelector("#yearInput").min = actualYear; setCurrentSeason(actualYear); const yearMenu = document.querySelector("#yearMenu"); const yearH2H = document.querySelector("#yearMenuH2H"); yearMenu.innerHTML = ""; yearH2H.innerHTML = ""; const timeTravel2026Enabled = seasonModData?.["time-travel-2026"] === "1" || seasonModData?.["time-travel-2026"] === 1; const minYear = timeTravel2026Enabled ? 2026 : game_version; // años (con data-year) for (let year = actualYear; year >= minYear; year--) { const a = document.createElement("a"); a.textContent = String(year); a.className = "redesigned-dropdown-item"; a.style.cursor = "pointer"; a.dataset.year = String(year); // <- aquí yearMenu.appendChild(a); a.addEventListener("click", () => manageRecordsSelected(a)); const a2 = document.createElement("a"); a2.textContent = String(year); a2.className = "redesigned-dropdown-item"; a2.style.cursor = "pointer"; a2.dataset.year = String(year); yearH2H.appendChild(a2); a2.addEventListener("click", () => { resetH2H(); document.querySelectorAll(".modal-team").forEach(el => el.classList.remove("d-none")); const yearBtnH2H = document.getElementById("yearButtonH2H"); yearBtnH2H.querySelector("span.dropdown-label").textContent = a2.textContent; yearBtnH2H.dataset.year = a2.dataset.year; // <- también lo guardo new Command("yearSelectedH2H", { year: a2.dataset.year }).execute(); }); } // All Time al principio (con data-year="all") const allTime = document.createElement("a"); allTime.textContent = "All Time"; allTime.className = "redesigned-dropdown-item"; allTime.id = "allTimeRecords"; allTime.dataset.year = "all"; // <- clave yearMenu.insertBefore(allTime, yearMenu.firstChild); allTime.addEventListener("click", () => manageRecordsSelected(allTime)); syncTableTypeDropdownChecks(); syncSeriesTypeDropdownChecks(); syncRecordsTypeDropdownChecks(); syncYearDropdownChecks(); } function manageRecordsSelected(forcedYearEl = null) { const yearMenu = document.querySelector("#yearMenu"); const yearItems = Array.from(yearMenu.querySelectorAll("a")); const yearBtn = document.getElementById("yearButton"); const typeVal = document.querySelector("#recordsTypeButton").dataset.value; updateTopPanelControlsVisibility(); // resolve seleccionado actual let selectedEl = forcedYearEl || yearItems.find(i => i.dataset.year === yearBtn.dataset.year) || yearItems[0]; const isAllTime = el => el.dataset.year === "all"; // si es standings y estaba en All Time, forzar primer año real if ((typeVal === "standings" || typeVal === "seasonreview" || typeVal === "sessionresults") && isAllTime(selectedEl)) { const firstReal = yearItems.find(i => !isAllTime(i)); if (firstReal) selectedEl = firstReal; } if (driverOrTeams === "teams" && isAllTime(selectedEl)) { const firstReal = yearItems.find(i => !isAllTime(i)); if (firstReal) selectedEl = firstReal; } // reflejar en el botón setYearButton(selectedEl); const selectedYear = selectedEl.dataset.year; const isCurrentYear = selectedYear === yearItems[1].dataset.year; console.log("Selected year:", selectedYear, "Type:", typeVal); if (typeVal === "standings") { isYearSelected = true manage_show_tables(); new Command("yearSelected", { year: selectedYear, isCurrentYear, formula: currentFormula }).execute(); } else if (typeVal === "seasonreview") { manageSeasonReview(); } else if (typeVal === "sessionresults") { return; } else { if (driverOrTeams === "teams" && ["wins", "poles", "podiums", "dotd"].includes(typeVal) && selectedYear !== "all") { new Command("teamRecordRequest", { type: typeVal, year: selectedYear, formula: currentFormula }).execute(); } else { new Command("recordSelected", { type: typeVal, year: selectedYear }).execute(); } manageShowRecords(); } } function manageSeasonReview(){ const driversTable = document.querySelector(".drivers-table") const teamsTable = document.querySelector(".teams-table") driversTable.classList.add("d-none") teamsTable.classList.add("d-none") const sessionResultsTable = document.querySelector(".session-results-table") if (sessionResultsTable) sessionResultsTable.classList.add("d-none") const recordsList = document.querySelector(".records-list") recordsList.classList.add("d-none") const seasonReviewBento = document.querySelector(".season-review-bento") seasonReviewBento.classList.remove("d-none") updateTopPanelControlsVisibility(); const driversTeamsPills = document.querySelector("#season_viewer .drivers-teams-pills"); if (driversTeamsPills) driversTeamsPills.classList.add("d-none"); const hideHistoric = document.querySelector("#season_viewer .hide-historic-drivers"); if (hideHistoric) hideHistoric.classList.add("d-none"); const selectedYear = document.getElementById("yearButton")?.dataset?.year; if (selectedYear) { const yearMenu = document.querySelector("#yearMenu"); const yearItems = yearMenu ? Array.from(yearMenu.querySelectorAll("a")) : []; const currentYear = yearItems[1]?.dataset?.year; const isCurrentYear = currentYear ? (selectedYear === currentYear) : true; new Command("seasonReviewSelected", { year: selectedYear, isCurrentYear, formula: currentFormula }).execute(); } } export function populateSeasonReview(data) { populateDriversStandingsSeasonReview(data.driversStandings, { events: data.events, pointsInfo: data.pointsInfo, lastRaceDoneId: data.lastRaceDoneId }) populateTeamsStandingsSeasonReview(data.teamsStandings, { events: data.events, pointsInfo: data.pointsInfo, lastRaceDoneId: data.lastRaceDoneId }) updateRoundsCounterSeasonReview(data.events) populateComparisonsSeasonReview(data.teamMateHeadToHead, data.teamsStandings) populateQualifyingAnalysisSeasonReview(data.qualifyingStageCounts) populateWinsDriversSeasonReview(data.winsRecords) populateTeamsAggregateSeasonReview((data.teamWinsTotals?.length ? data.teamWinsTotals : data.winsRecords), ".wins-teams-list", 4) populateTeamsAggregateSeasonReview((data.teamPolesTotals?.length ? data.teamPolesTotals : data.polesRecords), ".poles-teams-list", 4) populateDriverOfTheDaySeasonReview(data.driverOfTheDayCounts) populatePodiumsTeamsSeasonReview((data.teamPodiumsTotals?.length ? data.teamPodiumsTotals : data.podiumsRecords)) populatePodiumsDriversSeasonReview(data.podiumsRecords) } function updateRoundsCounterSeasonReview(events) { const totalEvents = Array.isArray(events) ? events.length : 0; // eventos que tienen event[3] == 2 const eventsDone = Array.isArray(events) ? events.filter(e => e[3] === 2).length : 0; document.querySelectorAll(".bento-title .first-number").forEach(el => { el.textContent = eventsDone; }); document.querySelectorAll(".bento-title .second-number").forEach(el => { el.textContent = totalEvents; }); } function populateComparisonsSeasonReview(comparisons, teamsStandings) { const raceComparisons = document.querySelector(".race-comparison"); const qualiComparisons = document.querySelector(".quali-comparison"); if (!raceComparisons || !qualiComparisons) return; raceComparisons.innerHTML = ""; qualiComparisons.innerHTML = ""; updateComparisonsMaxHeight(); ensureComparisonsHeightListener(); if (!Array.isArray(comparisons) || comparisons.length === 0) return; const startH2HFromSeasonReview = (driver1Id, driver2Id) => { const h2hPill = document.getElementById("h2hpill"); if (h2hPill) h2hPill.click(); const driversModePill = document.getElementById("driverspillmodal"); if (driversModePill) driversModePill.click(); const selectedYear = document.getElementById("yearButton")?.dataset?.year; const yearMenuH2H = document.getElementById("yearMenuH2H"); queueAutoCompareDrivers(driver1Id, driver2Id); if (selectedYear && yearMenuH2H) { const yearLink = Array.from(yearMenuH2H.querySelectorAll("a")) .find(a => (a.dataset.year || a.textContent)?.trim() === String(selectedYear)); if (yearLink) yearLink.click(); } }; const parseHeadToHead = (value) => { if (typeof value === "string") { const parts = value.split("-").map(x => parseInt(x, 10)); if (parts.length === 2) { return parts; } } return [0, 0]; }; const getSurname = (name) => { const words = (name || "").trim().split(/\s+/).filter(Boolean); return words.length ? words[words.length - 1] : ""; }; const teamPositionById = new Map(); if (Array.isArray(teamsStandings)) { teamsStandings.forEach((row) => { const teamId = Number(Array.isArray(row) ? row[0] : (row?.TeamID ?? row?.teamId)); const pos = Number(Array.isArray(row) ? row[1] : (row?.Position ?? row?.position)); teamPositionById.set(teamId, pos); }); } const ordered = [...comparisons].sort((a, b) => { const teamA = Number(a?.teamId ?? a?.TeamID ?? a?.teamID ?? -1); const teamB = Number(b?.teamId ?? b?.TeamID ?? b?.teamID ?? -1); const posA = teamPositionById.get(teamA) ?? 999; const posB = teamPositionById.get(teamB) ?? 999; return Number(posA) - Number(posB); }); const buildRow = (item, score1, score2) => { const teamId = Number(item?.teamId ?? item?.TeamID ?? item?.teamID ?? -1); const driver1Name = news_insert_space(item?.driver1Name ?? item?.Driver1Name ?? ""); const driver2Name = news_insert_space(item?.driver2Name ?? item?.Driver2Name ?? ""); const driver1Surname = getSurname(driver1Name); const driver2Surname = getSurname(driver2Name); const row = document.createElement("div"); row.className = "season-review-comparison-row"; const teamKey = team_dict[teamId]; if (teamKey) row.classList.add(teamKey); const name1Div = document.createElement("div"); name1Div.className = "season-review-comparison-name left"; const surname1Span = document.createElement("span"); surname1Span.className = "bold-font"; surname1Span.textContent = driver1Surname; name1Div.appendChild(surname1Span); row.appendChild(name1Div); const score1Div = document.createElement("div"); score1Div.className = "season-review-comparison-score"; score1Div.textContent = String(score1); row.appendChild(score1Div); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-team-logo-div" }); row.appendChild(logoDiv); } const score2Div = document.createElement("div"); score2Div.className = "season-review-comparison-score"; score2Div.textContent = String(score2); row.appendChild(score2Div); const name2Div = document.createElement("div"); name2Div.className = "season-review-comparison-name right"; const surname2Span = document.createElement("span"); surname2Span.className = "bold-font"; surname2Span.textContent = driver2Surname; name2Div.appendChild(surname2Span); row.appendChild(name2Div); const driver1Id = Number(item?.driver1Id ?? item?.Driver1ID ?? item?.Driver1Id); const driver2Id = Number(item?.driver2Id ?? item?.Driver2ID ?? item?.Driver2Id); if (driver1Id > 0 && driver2Id > 0) { row.addEventListener("click", () => startH2HFromSeasonReview(driver1Id, driver2Id)); } return row; }; ordered.forEach((item) => { let race1 = Number(item?.raceAhead1); let race2 = Number(item?.raceAhead2); if (!race1 && !race2) { [race1, race2] = parseHeadToHead(item?.raceHeadToHead); } let quali1 = Number(item?.qualiAhead1); let quali2 = Number(item?.qualiAhead2); if (!quali1 && !quali2) { [quali1, quali2] = parseHeadToHead(item?.qualiHeadToHead); } raceComparisons.appendChild(buildRow(item, race1, race2)); qualiComparisons.appendChild(buildRow(item, quali1, quali2)); }); updateComparisonsMaxHeight(); } function populateDriversStandingsSeasonReview(data, meta = {}) { const isF1 = currentFormula === 1; const standings = document.querySelector(".bento-driver-standings"); if (!standings) return; standings.innerHTML = ""; let leaderPts = data.length > 0 ? Number(data[0].Points) : 0; const secondPts = data.length > 1 ? Number(data[1].Points) : 0; let championClinched = false; if (isF1 && data.length > 1 && meta && Array.isArray(meta.events) && meta.events.length > 0 && meta.pointsInfo) { const lastRaceDoneId = Number(meta.lastRaceDoneId); const lastRaceIndex = meta.events.findIndex(x => Number(x?.[0]) === lastRaceDoneId); if (lastRaceIndex >= 0) { const racesLeft = meta.events.length - (lastRaceIndex + 1); const sprintsLeftLocal = meta.events.filter(x => Number(x?.[2]) === 1 && Number(x?.[0]) >= lastRaceDoneId).length; const maxRacePoints = Number(meta.pointsInfo?.twoBiggestPoints?.[0]?.[0] ?? meta.pointsInfo?.twoBiggestPoints?.[0] ?? 0); const isDoublePoints = Number(meta.pointsInfo?.isLastRaceDouble ?? meta.pointsInfo?.isLastraceDouble) === 1; const fastestLapBonus = Number(meta.pointsInfo?.fastestLapBonusPoint) === 1; const poleBonus = Number(meta.pointsInfo?.poleBonusPoint) === 1; const pointsRemaining = racesLeft * maxRacePoints + sprintsLeftLocal * 8 + (isDoublePoints ? maxRacePoints : 0) + (fastestLapBonus ? racesLeft : 0) + (poleBonus ? racesLeft : 0); championClinched = (leaderPts - secondPts) > pointsRemaining; } } data.forEach((driver, index) => { const driverDiv = document.createElement("div"); driverDiv.className = "season-review-driver"; const posDiv = document.createElement("div"); posDiv.className = "season-review-driver-position"; posDiv.textContent = driver.Position; driverDiv.appendChild(posDiv); const teamId = Number(driver.TeamID ?? driver.teamId ?? driver.teamID ?? driver.TeamId ?? -1); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1, wrapperClass: "drivers-table-logo-div season-review-driver-logo-div" }); driverDiv.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-driver-name"; const nameSpan = document.createElement("span"); const surnameSpan = document.createElement("span"); surnameSpan.className = "bold-font"; const fullName = news_insert_space(String(driver.DriverName ?? "")); const nameParts = fullName.split(" "); format_name(fullName, nameParts, nameSpan, surnameSpan, true); nameDiv.appendChild(nameSpan); nameDiv.appendChild(surnameSpan); driverDiv.appendChild(nameDiv); const pointsDiv = document.createElement("div"); pointsDiv.className = "season-review-driver-points"; pointsDiv.textContent = driver.Points; const pointsDiff = Number(driver.Points) - leaderPts; const diffDiv = document.createElement("div"); diffDiv.className = "season-review-driver-points diff"; diffDiv.textContent = index === 0 ? "" : `+${pointsDiff.toString().replace("-", "")}`; driverDiv.appendChild(diffDiv); driverDiv.appendChild(pointsDiv); if (index === 0 && championClinched) { posDiv.classList.add("champion"); pointsDiv.classList.add("champion"); } standings.appendChild(driverDiv); }); //calculate heiight and console log it let height = standings.getBoundingClientRect().height; console.log("Drivers Standings Height:", height); updateDriversStandingsMaxHeight(); ensureDriversStandingsHeightListener(); } function updateDriversStandingsMaxHeight() { const item = document.querySelector(".bento-item.item-1"); const standings = document.querySelector(".bento-driver-standings"); if (!item || !standings) return; const height = item.getBoundingClientRect().height; //if height is less than 60, maxHeight will be 570px const maxHeight = height <= 60 ? 570 : Math.max(0, Math.floor(height - 60)); standings.style.maxHeight = `${maxHeight}px`; standings.style.overflowX = "hidden"; standings.style.overflowY = "hidden"; standings.classList.remove("with-scrollbar"); const needsScroll = standings.scrollHeight > (standings.clientHeight + 4); if (needsScroll) { standings.style.overflowY = "auto"; standings.classList.add("with-scrollbar"); } } function ensureDriversStandingsHeightListener() { if (driversStandingsHeightListenerAttached) return; driversStandingsHeightListenerAttached = true; let rafId = null; window.addEventListener("resize", () => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; updateDriversStandingsMaxHeight(); }); }); } function updateTeamsStandingsMaxHeight() { const item = document.querySelector(".bento-item.item-2"); const standings = document.querySelector(".bento-team-standings, .bento-teams-standings"); if (!item || !standings) return; const height = item.getBoundingClientRect().height; // if height is less than 60, maxHeight will be 570px const maxHeight = height <= 60 ? 570 : Math.max(0, Math.floor(height - 60)); standings.style.maxHeight = `${maxHeight}px`; standings.style.overflowX = "hidden"; standings.style.overflowY = "hidden"; standings.classList.remove("with-scrollbar"); const needsScroll = standings.scrollHeight > (standings.clientHeight + 4); if (needsScroll) { standings.style.overflowY = "auto"; standings.classList.add("with-scrollbar"); } } function ensureTeamsStandingsHeightListener() { if (teamsStandingsHeightListenerAttached) return; teamsStandingsHeightListenerAttached = true; let rafId = null; window.addEventListener("resize", () => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; updateTeamsStandingsMaxHeight(); }); }); } function updateComparisonsMaxHeight() { const item = document.querySelector(".bento-item.item-4"); if (!item) return; const height = item.getBoundingClientRect().height; // if height is less than 60, maxHeight will be 570px const maxHeight = height <= 60 ? 570 : Math.max(0, Math.floor(height - 60)); document.querySelectorAll(".race-comparison, .quali-comparison").forEach((el) => { el.style.maxHeight = `${maxHeight}px`; el.style.height = `${maxHeight}px`; el.style.overflowX = "hidden"; el.style.overflowY = "hidden"; el.classList.remove("with-scrollbar"); const needsScroll = el.scrollHeight > (el.clientHeight + 4); if (needsScroll) { el.style.overflowY = "auto"; el.classList.add("with-scrollbar"); } }); } function ensureComparisonsHeightListener() { if (comparisonsHeightListenerAttached) return; comparisonsHeightListenerAttached = true; let rafId = null; window.addEventListener("resize", () => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; updateComparisonsMaxHeight(); }); }); } function populateTeamsStandingsSeasonReview(data, meta = {}) { if (!Array.isArray(data)) return; const container = document.querySelector(".bento-team-standings"); if (!container) return; container.innerHTML = ""; const leaderPts = data[0][2]; const secondPts = data.length > 1 ? Number((Array.isArray(data[1]) ? data[1][2] : (data[1]?.Points ?? data[1]?.points ?? 0))) : 0; let championClinched = false; if (currentFormula === 1 && data.length > 1 && meta && Array.isArray(meta.events) && meta.events.length > 0 && meta.pointsInfo) { const lastRaceDoneId = Number(meta.lastRaceDoneId); const lastRaceIndex = meta.events.findIndex(x => Number(x?.[0]) === lastRaceDoneId); if (lastRaceIndex >= 0) { const racesLeft = meta.events.length - (lastRaceIndex + 1); const sprintsLeftLocal = meta.events.filter(x => Number(x?.[2]) === 1 && Number(x?.[0]) >= lastRaceDoneId).length; const maxFirstPoints = Number(meta.pointsInfo?.twoBiggestPoints?.[0]?.[0] ?? meta.pointsInfo?.twoBiggestPoints?.[0] ?? 0); const maxSecondPoints = Number(meta.pointsInfo?.twoBiggestPoints?.[1]?.[0] ?? meta.pointsInfo?.twoBiggestPoints?.[1] ?? 0); const maxTeamRacePoints = maxFirstPoints + maxSecondPoints; const isDoublePoints = Number(meta.pointsInfo?.isLastRaceDouble ?? meta.pointsInfo?.isLastraceDouble) === 1; const fastestLapBonus = Number(meta.pointsInfo?.fastestLapBonusPoint) === 1; const poleBonus = Number(meta.pointsInfo?.poleBonusPoint) === 1; const pointsRemaining = racesLeft * maxTeamRacePoints + sprintsLeftLocal * 15 + (isDoublePoints ? maxTeamRacePoints : 0) + (fastestLapBonus ? racesLeft : 0) + (poleBonus ? racesLeft : 0); championClinched = (Number(leaderPts) - Number(secondPts)) > pointsRemaining; } } data.forEach((team, index) => { const teamObj = Array.isArray(team) ? { TeamID: team[0], Position: team[1], Points: team[2] } : team; const teamDiv = document.createElement("div"); teamDiv.className = "season-review-team"; const posDiv = document.createElement("div"); posDiv.className = "season-review-team-position"; posDiv.textContent = teamObj.Position ?? teamObj.position ?? ""; teamDiv.appendChild(posDiv); const teamId = Number(teamObj.TeamID ?? teamObj.teamId ?? teamObj.teamID ?? teamObj.TeamId ?? -1); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-team-logo-div" }); teamDiv.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-team-name bold-font"; nameDiv.textContent = formatTeamNameForDisplay(combined_dict[teamId] || ""); teamDiv.appendChild(nameDiv); const pointsDiv = document.createElement("div"); pointsDiv.className = "season-review-team-points"; pointsDiv.textContent = teamObj.Points ?? teamObj.points ?? ""; const pointsDiff = Number(teamObj.Points) - leaderPts; const diffDiv = document.createElement("div"); diffDiv.className = "season-review-team-points diff"; diffDiv.textContent = index === 0 ? "" : `+${pointsDiff.toString().replace("-", "")}`; teamDiv.appendChild(diffDiv); teamDiv.appendChild(pointsDiv); const position = Number(posDiv.textContent); if (position === 1 && championClinched) { posDiv.classList.add("champion"); pointsDiv.classList.add("champion"); } container.appendChild(teamDiv); }); updateTeamsStandingsMaxHeight(); ensureTeamsStandingsHeightListener(); } function populateQualifyingAnalysisSeasonReview(data) { const polesContainer = document.querySelector(".poles-comparison"); const q3Container = document.querySelector(".q3-comparison"); const q2Container = document.querySelector(".q2-comparison"); if (!polesContainer || !q3Container || !q2Container) return; polesContainer.innerHTML = ""; q3Container.innerHTML = ""; q2Container.innerHTML = ""; if (!Array.isArray(data) || data.length === 0) return; const getSurname = (name) => { const words = (name || "").trim().split(/\s+/).filter(Boolean); return words.length ? words[words.length - 1] : ""; }; const buildRow = (entry, position, count) => { const teamId = Number(entry?.teamId ?? entry?.TeamID ?? entry?.teamID ?? -1); const fullName = news_insert_space(entry?.name ?? entry?.DriverName ?? ""); const surname = getSurname(fullName); const row = document.createElement("div"); row.className = "season-review-qualifying-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-qualifying-position"; posDiv.textContent = String(position); row.appendChild(posDiv); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-driver-logo-div" }); row.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-qualifying-name"; const surnameSpan = document.createElement("span"); surnameSpan.className = "bold-font"; surnameSpan.textContent = surname; nameDiv.appendChild(surnameSpan); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-qualifying-count"; countDiv.textContent = String(count); row.appendChild(countDiv); return row; }; const byKey = (key) => { return [...data].filter((d) => (Number(d?.[key]) || 0) > 0).sort((a, b) => { const av = Number(a?.[key]) || 0; const bv = Number(b?.[key]) || 0; if (bv !== av) return bv - av; return String(a?.name ?? "").localeCompare(String(b?.name ?? "")); }); }; const poles = byKey("poleCount"); const q3s = byKey("q3Count"); const q2s = byKey("q2Count"); poles.forEach((d, idx) => polesContainer.appendChild(buildRow(d, idx + 1, Number(d?.poleCount) || 0))); q3s.forEach((d, idx) => q3Container.appendChild(buildRow(d, idx + 1, Number(d?.q3Count) || 0))); q2s.forEach((d, idx) => q2Container.appendChild(buildRow(d, idx + 1, Number(d?.q2Count) || 0))); updateQualifyingListsMaxHeight(); ensureQualifyingListsHeightListener(); } function updateQualifyingListsMaxHeight() { const item = document.querySelector(".bento-item.item-3"); if (!item) return; const height = item.getBoundingClientRect().height; //if height is less than 60, maxHeight will be 250px const maxHeight = height < 60 ? 250 : Math.max(0, Math.floor(height - 60)); document.querySelectorAll(".poles-comparison, .q3-comparison, .q2-comparison").forEach((el) => { el.style.maxHeight = `${maxHeight}px`; el.style.overflowX = "hidden"; el.style.overflowY = "hidden"; el.classList.remove("with-scrollbar"); const needsScroll = el.scrollHeight > (el.clientHeight + 4); if (needsScroll) { el.style.overflowY = "auto"; el.classList.add("with-scrollbar"); } }); } function ensureQualifyingListsHeightListener() { if (qualifyingHeightListenerAttached) return; qualifyingHeightListenerAttached = true; let rafId = null; window.addEventListener("resize", () => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; updateQualifyingListsMaxHeight(); }); }); } function populateWinsDriversSeasonReview(data) { const container = document.querySelector(".wins-drivers-list"); if (!container) return; container.innerHTML = ""; if (!Array.isArray(data) || data.length === 0) return; const ordered = [...data] .filter(r => Number(r?.value) > 0) .sort((a, b) => (Number(b?.value) || 0) - (Number(a?.value) || 0)); ordered.forEach((record, idx) => { const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const teamId = Number(record?.teamId ?? -1); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-driver-logo-div" }); row.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name"; const fullName = news_insert_space(record?.name ?? ""); const surname = fullName.split(" ").pop(); const nameSpan = document.createElement("span"); nameSpan.textContent = fullName.replace(surname, ""); const surnameSpan = document.createElement("span"); surnameSpan.textContent = surname; surnameSpan.className = "bold-font"; nameDiv.appendChild(nameSpan); nameDiv.appendChild(surnameSpan); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(record?.value ?? 0); row.appendChild(countDiv); container.appendChild(row); }); updateWinsDriversListMaxHeight(); ensureWinsDriversListHeightListener(); } function updateWinsDriversListMaxHeight() { const item = document.querySelector(".bento-item.item-5"); const list = document.querySelector(".wins-drivers-list"); if (!item || !list) return; const height = item.getBoundingClientRect().height; //if height is less than 60, maxHeight will be 300px const maxHeight = height < 60 ? 300 : Math.max(0, Math.floor(height - 60)); list.style.maxHeight = `${maxHeight}px`; list.style.overflowX = "hidden"; list.style.overflowY = "hidden"; const needsScroll = list.scrollHeight > (list.clientHeight + 4); if (needsScroll) { list.style.overflowY = "auto"; } } function ensureWinsDriversListHeightListener() { if (winsHeightListenerAttached) return; winsHeightListenerAttached = true; let rafId = null; window.addEventListener("resize", () => { if (rafId) cancelAnimationFrame(rafId); rafId = requestAnimationFrame(() => { rafId = null; updateWinsDriversListMaxHeight(); }); }); } function formatTeamNameForDisplay(teamName, { upper = false } = {}) { const raw = String(teamName ?? "").trim(); const rawUpper = raw.toUpperCase(); const shortened = rawUpper === "VISA CASHAPP RB" ? "VCARB" : raw; return upper ? shortened.toUpperCase() : shortened; } function addSeasonReviewPhantomRows(container, targetCount = 4) { if (!container) return; const currentCount = container.children.length; for (let i = currentCount; i < targetCount; i++) { const phantom = document.createElement("div"); phantom.className = "season-review-wins-row phantom-row"; container.appendChild(phantom); } } function populateTeamsAggregateSeasonReview(driverRecords, containerSelector, limit = 4) { const container = document.querySelector(containerSelector); if (!container) return; container.innerHTML = ""; if (!Array.isArray(driverRecords) || driverRecords.length === 0) { addSeasonReviewPhantomRows(container, 4); return; } const totalsByTeamId = new Map(); driverRecords.forEach((rec) => { const teamId = Number(rec?.teamId ?? -1); const value = Number(rec?.value ?? 0); if (teamId === -1) return; if (value <= 0) return; totalsByTeamId.set(teamId, (totalsByTeamId.get(teamId) || 0) + value); }); const ordered = [...totalsByTeamId.entries()] .map(([teamId, value]) => ({ teamId: Number(teamId), value: Number(value) })) .sort((a, b) => (b.value - a.value) || String(combined_dict[a.teamId] || "").localeCompare(String(combined_dict[b.teamId] || ""))) .slice(0, limit); ordered.forEach((team, idx) => { const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const logoDiv = buildDriverLogoDiv(team.teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-team-logo-div" }); row.appendChild(logoDiv); const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name bold-font"; const teamName = combined_dict[team.teamId] || ""; nameDiv.textContent = formatTeamNameForDisplay(teamName, { upper: true }); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(team.value); row.appendChild(countDiv); container.appendChild(row); }); addSeasonReviewPhantomRows(container, 4); } function populateDriverOfTheDaySeasonReview(data) { const left = document.querySelector(".dotd-drivers-half-1"); const right = document.querySelector(".dotd-drivers-half-2"); if (!left || !right) return; left.innerHTML = ""; right.innerHTML = ""; if (!Array.isArray(data) || data.length === 0) { addSeasonReviewPhantomRows(left, 4); addSeasonReviewPhantomRows(right, 4); return; } const ordered = [...data] .filter(d => Number(d?.count) > 0) .sort((a, b) => (Number(b?.count) || 0) - (Number(a?.count) || 0)); const top = ordered.slice(0, 8); top.forEach((d, idx) => { const half = idx < 4 ? left : right; const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const teamId = Number(d?.teamId ?? -1); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-driver-logo-div" }); row.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name"; const fullName = news_insert_space(d?.name ?? ""); const surname = fullName.split(" ").pop(); const surnameSpan = document.createElement("span"); surnameSpan.className = "bold-font"; surnameSpan.textContent = surname; nameDiv.appendChild(surnameSpan); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(d?.count ?? 0); row.appendChild(countDiv); half.appendChild(row); }); addSeasonReviewPhantomRows(left, 4); addSeasonReviewPhantomRows(right, 4); } function computeTeamTotalsFromDriverRecords(driverRecords) { const totalsByTeamId = new Map(); (driverRecords || []).forEach((rec) => { const teamId = Number(rec?.teamId ?? -1); const value = Number(rec?.value ?? 0); if (teamId === -1) return; if (value <= 0) return; totalsByTeamId.set(teamId, (totalsByTeamId.get(teamId) || 0) + value); }); return [...totalsByTeamId.entries()] .map(([teamId, value]) => ({ teamId: Number(teamId), value: Number(value) })) .sort((a, b) => (b.value - a.value) || String(combined_dict[a.teamId] || "").localeCompare(String(combined_dict[b.teamId] || ""))); } function populateWinsTeamsSeasonReview(winsRecords) { const left = document.querySelector(".wins-teams-half-1"); const right = document.querySelector(".wins-teams-half-2"); if (!left || !right) return; left.innerHTML = ""; right.innerHTML = ""; const ordered = computeTeamTotalsFromDriverRecords(winsRecords).slice(0, 8); ordered.forEach((team, idx) => { const half = idx < 4 ? left : right; const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const logoDiv = buildDriverLogoDiv(team.teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-team-logo-div" }); row.appendChild(logoDiv); const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name bold-font"; nameDiv.textContent = formatTeamNameForDisplay(combined_dict[team.teamId] || "", { upper: true }); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(team.value); row.appendChild(countDiv); half.appendChild(row); }); } function populatePodiumsTeamsSeasonReview(podiumsRecords) { const left = document.querySelector(".podiums-teams-half-1"); const right = document.querySelector(".podiums-teams-half-2"); if (!left || !right) return; left.innerHTML = ""; right.innerHTML = ""; const ordered = computeTeamTotalsFromDriverRecords(podiumsRecords).slice(0, 8); ordered.forEach((team, idx) => { const half = idx < 4 ? left : right; const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const logoDiv = buildDriverLogoDiv(team.teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-team-logo-div" }); row.appendChild(logoDiv); const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name bold-font"; nameDiv.textContent = formatTeamNameForDisplay(combined_dict[team.teamId] || "", { upper: true }); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(team.value); row.appendChild(countDiv); half.appendChild(row); }); addSeasonReviewPhantomRows(left, 4); addSeasonReviewPhantomRows(right, 4); } function populatePodiumsDriversSeasonReview(podiumsRecords) { const left = document.querySelector(".podiums-drivers-half-1"); const right = document.querySelector(".podiums-drivers-half-2"); if (!left || !right) return; left.innerHTML = ""; right.innerHTML = ""; if (!Array.isArray(podiumsRecords) || podiumsRecords.length === 0) { addSeasonReviewPhantomRows(left, 4); addSeasonReviewPhantomRows(right, 4); return; } const ordered = [...podiumsRecords] .filter(r => Number(r?.value) > 0) .sort((a, b) => (Number(b?.value) || 0) - (Number(a?.value) || 0)) .slice(0, 8); ordered.forEach((record, idx) => { const half = idx < 4 ? left : right; const row = document.createElement("div"); row.className = "season-review-wins-row"; const posDiv = document.createElement("div"); posDiv.className = "season-review-wins-position"; posDiv.textContent = String(idx + 1); row.appendChild(posDiv); const teamId = Number(record?.teamId ?? -1); if (teamId !== -1) { const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div season-review-driver-logo-div" }); row.appendChild(logoDiv); } const nameDiv = document.createElement("div"); nameDiv.className = "season-review-wins-name"; const fullName = news_insert_space(record?.name ?? ""); const surname = fullName.split(" ").pop(); const surnameSpan = document.createElement("span"); surnameSpan.className = "bold-font"; surnameSpan.textContent = surname; nameDiv.appendChild(surnameSpan); row.appendChild(nameDiv); const countDiv = document.createElement("div"); countDiv.className = "season-review-wins-count"; countDiv.textContent = String(record?.value ?? 0); row.appendChild(countDiv); half.appendChild(row); }); addSeasonReviewPhantomRows(left, 4); addSeasonReviewPhantomRows(right, 4); } document.querySelector(".bento-grid .item-1").addEventListener("click", () => { const driversTablePill = document.querySelector("#driverspill"); if (driversTablePill) driversTablePill.click(); const standingsPill = document.querySelector("#standingspill"); if (standingsPill) standingsPill.click(); }); document.querySelector(".bento-grid .item-2").addEventListener("click", () => { const teamsTablePill = document.querySelector("#teamspill"); if (teamsTablePill) teamsTablePill.click(); const standingsPill = document.querySelector("#standingspill"); if (standingsPill) standingsPill.click(); }); document.querySelector(".bento-grid .item-10").addEventListener("click", () => { const teamsTablePill = document.querySelector("#teamspill"); if (teamsTablePill) teamsTablePill.click(); const polesPill = document.querySelector("#polespill"); if (polesPill) polesPill.click(); }); document.querySelector(".bento-grid .item-3").addEventListener("click", () => { const driversTablePill = document.querySelector("#driverspill"); if (driversTablePill) driversTablePill.click(); const polesPill = document.querySelector("#polespill"); if (polesPill) polesPill.click(); }); document.querySelector(".bento-grid .item-6").addEventListener("click", () => { const teamsTablePill = document.querySelector("#teamspill"); if (teamsTablePill) teamsTablePill.click(); const winsPill = document.querySelector("#winspill"); if (winsPill) winsPill.click(); }); document.querySelector(".bento-grid .item-5").addEventListener("click", () => { const driversTablePill = document.querySelector("#driverspill"); if (driversTablePill) driversTablePill.click(); const winsPill = document.querySelector("#winspill"); if (winsPill) winsPill.click(); }); document.querySelector(".bento-grid .item-8").addEventListener("click", () => { const teamsTablePill = document.querySelector("#teamspill"); if (teamsTablePill) teamsTablePill.click(); const podiumPill = document.querySelector("#podiumspill"); if (podiumPill) podiumPill.click(); }); document.querySelector(".bento-grid .item-7").addEventListener("click", () => { const driversTablePill = document.querySelector("#driverspill"); if (driversTablePill) driversTablePill.click(); const dotdPill = document.querySelector("#dotdpill"); if (dotdPill) dotdPill.click(); }); document.querySelector(".bento-grid .item-9").addEventListener("click", () => { const driversTablePill = document.querySelector("#driverspill"); if (driversTablePill) driversTablePill.click(); const podiumPill = document.querySelector("#podiumspill"); if (podiumPill) podiumPill.click(); }); function manageShowRecords() { const driversTable = document.querySelector(".drivers-table") const teamsTable = document.querySelector(".teams-table") driversTable.classList.add("d-none") teamsTable.classList.add("d-none") const seasonReviewBento = document.querySelector(".season-review-bento") seasonReviewBento.classList.add("d-none") const sessionResultsTable = document.querySelector(".session-results-table") if (sessionResultsTable) sessionResultsTable.classList.add("d-none") const recordsList = document.querySelector(".records-list") recordsList.classList.remove("d-none") recordsList.innerHTML = "" updateTopPanelControlsVisibility(); } function showSessionResultsTable() { const driversTable = document.querySelector(".drivers-table") const teamsTable = document.querySelector(".teams-table") const seasonReviewBento = document.querySelector(".season-review-bento") const recordsList = document.querySelector(".records-list") const sessionResultsTable = document.querySelector(".session-results-table") if (driversTable) driversTable.classList.add("d-none") if (teamsTable) teamsTable.classList.add("d-none") if (seasonReviewBento) seasonReviewBento.classList.add("d-none") if (recordsList) recordsList.classList.add("d-none") if (sessionResultsTable) sessionResultsTable.classList.remove("d-none") updateTopPanelControlsVisibility(); } function getSessionResultsQualiGridPosition(row) { const explicitGridPosition = Number(row?.gridPosition); if (explicitGridPosition > 0) return explicitGridPosition; const qualifyingPos = Number(row?.pos); const gridPenalty = Number(row?.gridPenalty); const computedGridPosition = gridPenalty > 0 ? 0 : qualifyingPos; return computedGridPosition > 0 ? computedGridPosition : 0; } export function onSessionResultsFetched(data) { sessionResultsLastFetched = data; const meta = data?.meta || {}; const headerMain = document.querySelector(".session-results-title-main"); const headerSession = document.querySelector(".session-results-title-session"); const headerRound = document.querySelector(".session-results-title-round"); const titleEl = document.querySelector(".session-results-title"); const editToggle = document.getElementById("sessionResultsEditToggle"); if (headerMain && headerSession) { const year = String(data?.year ?? "").trim(); const trackId = meta?.trackId; const gpNameRaw = getGpDisplayName(trackId); const gpNameRawStr = String(gpNameRaw || "").trim(); let gpName = gpNameRawStr; if (/\bgrand prix\b/i.test(gpNameRawStr)) { gpName = gpNameRawStr; } else if (/\bgp\b/i.test(gpNameRawStr)) { gpName = gpNameRawStr.replace(/\bgp\b/ig, "Grand Prix"); } else { gpName = `${gpNameRawStr} Grand Prix`; } gpName = gpName.trim(); const weekendType = meta?.weekendType; const sessionKey = String(data?.sessionKey ?? meta?.sessionKey ?? "").trim(); const sessionLabel = getSessionOptionsForWeekend(weekendType).find(o => o.key === sessionKey)?.label || sessionKey || ""; headerMain.textContent = `${year} ${gpName}`.trim(); headerSession.textContent = String(sessionLabel); } if (headerRound) { const yearKey = String(data?.year ?? "").trim(); const events = sessionResultsEventsCache.get(yearKey); const raceId = Number(data?.raceId ?? meta?.raceId); const total = Array.isArray(events) ? events.length : 0; const idx = Array.isArray(events) ? events.findIndex(e => Number(e?.[0]) === raceId) : -1; headerRound.textContent = (total > 0 && idx >= 0) ? `ROUND ${idx + 1}/${total}` : ""; } if (titleEl) { const trackId = Number(meta?.trackId); const trackCode = races_names?.[trackId]; const flagKey = trackCode ? `${String(trackCode).toLowerCase()}0` : null; const flagPath = flagKey ? codes_dict?.[flagKey] : null; const useTallFlagHeader = ["HUN", "NED", "UAE"].includes(String(trackCode || "").toUpperCase()); titleEl.style.setProperty( "--session-results-flag-bg", flagPath ? `url("${String(flagPath)}")` : "none" ); titleEl.classList.toggle("session-results-title-tall-flag", useTallFlagHeader); } document.querySelectorAll(".session-results-table .session-results-rows .session-results-row").forEach((el) => el.remove()); const rawResults = data?.results ?? data; const results = Array.isArray(rawResults) ? rawResults : []; const sessionKeyLower = String(data?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); const isMainRaceSession = sessionKeyLower === "race"; const isRaceSession = sessionKeyLower === "race" || sessionKeyLower === "sprintrace"; const isQualiSession = sessionKeyLower === "quali" || sessionKeyLower === "sprintquali"; const isPracticeSession = sessionKeyLower === "fp" || sessionKeyLower === "fp1" || sessionKeyLower === "fp2" || sessionKeyLower === "fp3"; const isEditableResultsSession = isMainRaceSession || sessionKeyLower === "sprintrace"; const isEditRace = sessionResultsEditMode && isEditableResultsSession; const dotdDriverId = Number(meta?.dotdDriverId); const showPointsColumn = !isPracticeSession && (!isQualiSession || Number(meta?.hasPolePositionPoints) === 1); const hasQualiGrid = isQualiSession && results.some((r) => getSessionResultsQualiGridPosition(r) > 0); if (!isEditableResultsSession && sessionResultsEditMode) { sessionResultsEditMode = false; manageSaveButton(false); } if (editToggle) editToggle.classList.toggle("d-none", !isEditableResultsSession); if (editToggle) { const label = editToggle.querySelector("span"); if (label) label.textContent = sessionResultsEditMode && isMainRaceSession ? "Cancel" : "Edit"; } const sessionResultsTable = document.querySelector(".session-results-table"); const hasGrid = results.some((r) => { const grid = Number(r?.grid); return grid > 0; }); const hasTime = results.some((r) => { const time = Number(r?.time); return time > 0; }); if (sessionResultsTable) { sessionResultsTable.classList.toggle("no-grid", !hasGrid); sessionResultsTable.classList.toggle("no-time", !hasTime); sessionResultsTable.classList.toggle("is-quali", isQualiSession); sessionResultsTable.classList.toggle("is-practice", isPracticeSession); sessionResultsTable.classList.toggle("is-edit", isEditRace); sessionResultsTable.classList.toggle("is-compact", sessionResultsCompactMode); sessionResultsTable.classList.toggle("no-points", !showPointsColumn); sessionResultsTable.classList.toggle("has-quali-grid", hasQualiGrid); } const leaderRow = results.find(r => Number(r?.pos) === 1) || results[0] || null; const leaderTime = leaderRow ? Number(leaderRow?.time) : null; const leaderLaps = leaderRow ? Number(leaderRow?.laps) : null; const fastestLapBest = results .map(r => Number(r?.fastestLap)) .filter(v => v > 0) .reduce((min, v) => (min == null || v < min ? v : min), null); const q1Best = results .map(r => Number(r?.q1FastestLap)) .filter(v => v > 0) .reduce((min, v) => (min == null || v < min ? v : min), null); const q2Best = results .map(r => Number(r?.q2FastestLap)) .filter(v => v > 0) .reduce((min, v) => (min == null || v < min ? v : min), null); const q3Best = results .map(r => Number(r?.q3FastestLap)) .filter(v => v > 0) .reduce((min, v) => (min == null || v < min ? v : min), null); results.forEach((row, idx) => { const sessionResultRow = document.createElement("div"); sessionResultRow.className = "session-results-row"; if (idx % 2 === 1) sessionResultRow.classList.add("odd"); if (row?.driverId != null) sessionResultRow.dataset.driverid = String(row.driverId); sessionResultRow.dataset.dnf = Number(row?.dnf) === 1 ? "1" : "0"; const dragDiv = document.createElement("div"); dragDiv.className = "session-results-drag session-results-cell"; const dragIcon = document.createElement("div"); dragIcon.className = "session-results-drag-icon"; dragDiv.appendChild(dragIcon); if (!isEditRace) { dragDiv.classList.add("hidden"); } else { dragDiv.classList.add("session-results-drag-handle"); dragDiv.draggable = true; dragDiv.addEventListener("dragstart", (e) => { sessionResultsDraggedRow = sessionResultRow; sessionResultRow.classList.add("dragging"); e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", ""); }); dragDiv.addEventListener("dragend", () => { sessionResultRow.classList.remove("dragging"); sessionResultsDraggedRow = null; if (sessionResultsDragRaf) { cancelAnimationFrame(sessionResultsDragRaf); sessionResultsDragRaf = 0; } const container = document.querySelector(".session-results-table .session-results-rows"); if (container) { reindexRaceEditPositions(container); updateRaceEditPoints(); } }); } sessionResultRow.appendChild(dragDiv); const posDiv = document.createElement("div"); posDiv.className = "session-results-position"; posDiv.classList.add("session-results-cell"); const pos = Number(row?.pos); posDiv.textContent = String(pos); if (pos === 1) sessionResultRow.classList.add("leader"); if (pos === 1) posDiv.classList.add("champion"); else if (pos === 2) posDiv.classList.add("second"); else if (pos === 3) posDiv.classList.add("third"); if (isQualiSession) { const q2CutPos = custom_team ? 16 : 15; if (pos === q2CutPos) sessionResultRow.classList.add("quali-cut-q2"); if (pos === 10) sessionResultRow.classList.add("quali-cut-q3"); } sessionResultRow.appendChild(posDiv); const gainedLostDiv = document.createElement("div"); gainedLostDiv.className = "session-results-gained-lost"; gainedLostDiv.classList.add("session-results-cell"); const gainedLostNumber = document.createElement("span"); gainedLostNumber.className = "session-results-gained-lost-number"; const gainedLostIcon = document.createElement("i"); gainedLostDiv.appendChild(gainedLostNumber); gainedLostDiv.appendChild(gainedLostIcon); const grid = Number(row?.grid); const gained = grid - pos; if (gained > 0) { gainedLostNumber.textContent = `${gained}`; gainedLostIcon.className = "bi bi-caret-up-fill"; gainedLostDiv.classList.add("up"); } else if (gained < 0) { gainedLostNumber.textContent = `${-gained}`; gainedLostIcon.className = "bi bi-caret-down-fill"; gainedLostDiv.classList.add("down"); } else { gainedLostNumber.textContent = ""; gainedLostIcon.className = "bi bi-dash"; gainedLostDiv.classList.add("neutral"); } if (!hasGrid) gainedLostDiv.classList.add("hidden"); sessionResultRow.appendChild(gainedLostDiv); const driverDiv = document.createElement("div"); driverDiv.className = "session-results-driver-name"; driverDiv.classList.add("session-results-cell"); const nat = String(row?.nationality ?? "").trim(); if (nat) { const img = document.createElement("img"); img.className = "session-results-nationality-flag"; img.alt = nat; img.loading = "lazy"; img.decoding = "async"; img.src = `https://flagsapi.com/${nat}/flat/64.png`; driverDiv.appendChild(img); } const driverName = String(row?.name ?? ""); const nameParts = driverName.split(" "); const nameSpan = document.createElement("span"); nameSpan.className = "session-results-first-name"; const surnameSpan = document.createElement("span"); surnameSpan.className = "bold-font session-results-surname"; format_name(driverName, nameParts, nameSpan, surnameSpan); surnameSpan.textContent = String(surnameSpan.textContent || "").toUpperCase(); driverDiv.appendChild(nameSpan); driverDiv.appendChild(surnameSpan); if (isMainRaceSession && dotdDriverId > 0 && Number(row?.driverId) === dotdDriverId) { const dotdSpan = document.createElement("span"); dotdSpan.className = "session-results-dotd"; dotdSpan.textContent = "DotD"; driverDiv.appendChild(dotdSpan); } sessionResultRow.appendChild(driverDiv); const teamId = Number(row?.teamId ?? -1); const teamDiv = document.createElement("div"); teamDiv.className = "session-results-team"; teamDiv.classList.add("session-results-cell"); const logoDiv = teamId !== -1 ? buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div session-results-driver-logo-div" }) : (() => { const empty = document.createElement("div"); empty.className = "drivers-table-logo-div session-results-driver-logo-div"; return empty; })(); teamDiv.appendChild(logoDiv); const teamNameSpan = document.createElement("span"); teamNameSpan.className = "session-results-team-name"; teamNameSpan.textContent = (teamId !== -1) ? formatTeamNameForDisplay((combined_dict?.[teamId] ?? ""), { upper: true }) : ""; teamDiv.appendChild(teamNameSpan); const engineNameSpan = document.createElement("span"); engineNameSpan.className = "session-results-engine-name"; if (currentFormula === 1 && teamId !== -1) { const engineId = engine_allocations?.[teamId]; const engineName = engineId != null ? engine_names?.[engineId] : ""; engineNameSpan.textContent = String(engineName || "").toUpperCase(); } teamDiv.appendChild(engineNameSpan); sessionResultRow.appendChild(teamDiv); const spacerDiv = document.createElement("div"); spacerDiv.className = "session-results-spacer"; sessionResultRow.appendChild(spacerDiv); const q1Div = document.createElement("div"); q1Div.className = "session-results-quali-lap session-results-q1"; q1Div.classList.add("session-results-cell"); const q1 = Number(row?.q1FastestLap); q1Div.innerText = q1 > 0 ? formatLapTime(q1) : "-"; if (q1Best != null && q1 > 0 && Math.abs(q1 - q1Best) < 1e-6) q1Div.classList.add("fastest"); if (!isQualiSession) q1Div.classList.add("hidden"); sessionResultRow.appendChild(q1Div); const q2Div = document.createElement("div"); q2Div.className = "session-results-quali-lap session-results-q2"; q2Div.classList.add("session-results-cell"); const q2 = Number(row?.q2FastestLap); q2Div.innerText = q2 > 0 ? formatLapTime(q2) : "-"; if (q2Best != null && q2 > 0 && Math.abs(q2 - q2Best) < 1e-6) q2Div.classList.add("fastest"); if (!isQualiSession) q2Div.classList.add("hidden"); sessionResultRow.appendChild(q2Div); //for q3, display difference (without any 0 in front, so if 0:01.223 should be 1.223) to q3best instead of lap time, if q3best is available and the lap time is valid const q3Div = document.createElement("div"); q3Div.className = "session-results-quali-lap session-results-q3"; q3Div.classList.add("session-results-cell"); const q3 = Number(row?.q3FastestLap); if (q3Best != null && q3 > 0 && Math.abs(q3 - q3Best) < 1e-6) q3Div.classList.add("fastest"); if (q3Best != null && q3 > 0) { const q3Gap = q3 - q3Best; q3Div.innerText = q3Gap > 0 ? `+${q3Gap.toFixed(3)}` : formatLapTime(q3); } else { q3Div.innerText = q3 > 0 ? formatLapTime(q3) : "-"; } if (!isQualiSession) q3Div.classList.add("hidden"); sessionResultRow.appendChild(q3Div); const lapsDiv = document.createElement("div"); lapsDiv.className = "session-results-laps"; lapsDiv.classList.add("session-results-cell"); const laps = Number(row?.laps); lapsDiv.innerText = laps >= 0 ? String(laps) : "-"; if (!isPracticeSession) lapsDiv.classList.add("hidden"); sessionResultRow.appendChild(lapsDiv); const fastestLapDiv = document.createElement("div"); fastestLapDiv.className = "session-results-fastest-lap"; fastestLapDiv.classList.add("session-results-cell"); if (isPracticeSession) fastestLapDiv.classList.add("session-results-fastest-lap-practice"); const fl = Number(row?.fastestLap); if (fl > 0) { if (isPracticeSession && fastestLapBest != null) { const flGap = fl - fastestLapBest; fastestLapDiv.innerText = flGap > 0 ? `+${flGap.toFixed(3)}` : formatLapTime(fl); } else { fastestLapDiv.innerText = formatLapTime(fl); } if (fastestLapBest != null && Math.abs(fl - fastestLapBest) < 1e-6) { fastestLapDiv.classList.add("fastest"); } } if (isQualiSession) fastestLapDiv.classList.add("hidden"); sessionResultRow.appendChild(fastestLapDiv); const timeDiv = document.createElement("div"); timeDiv.className = "session-results-time"; timeDiv.classList.add("session-results-cell"); if (isEditRace) { const dnf = Number(row?.dnf) === 1; const rowTime = Number(row?.time); const input = document.createElement("input"); input.className = "session-results-time-input"; input.type = "text"; input.value = dnf ? "DNF" : ((rowTime > 0) ? formatLapTime(rowTime) : ""); input.placeholder = "DNF or 0:00.000"; input.addEventListener("input", () => { const v = String(input.value || "").trim(); const isDnf = /^dnf$/i.test(v); sessionResultRow.dataset.dnf = isDnf ? "1" : "0"; updateRaceEditPoints(); }); timeDiv.appendChild(input); } else if (isRaceSession) { const dnf = Number(row?.dnf) === 1; const rowPos = Number(row?.pos); const rowTime = Number(row?.time); const rowLaps = Number(row?.laps); if (dnf) { timeDiv.innerText = "-"; } else if (rowLaps < leaderLaps) { timeDiv.innerText = `+${leaderLaps - rowLaps}L`; } else if (rowPos === 1 && rowTime > 0) { timeDiv.innerText = formatLapTime(rowTime); } else if (leaderTime > 0 && rowTime > 0) { const gap = rowTime - leaderTime; timeDiv.innerText = gap > 0 ? `+${gap.toFixed(3)}` : "-"; } else { timeDiv.innerText = "-"; } } else { const rowTime = Number(row?.time); timeDiv.innerText = rowTime > 0 ? formatLapTime(rowTime) : "-"; } if (!hasTime) timeDiv.classList.add("hidden"); if (isQualiSession) timeDiv.classList.add("hidden"); sessionResultRow.appendChild(timeDiv); const qualiGridDiv = document.createElement("div"); qualiGridDiv.className = "session-results-grid-position"; qualiGridDiv.classList.add("session-results-cell"); const qualiGridPosition = getSessionResultsQualiGridPosition(row); const gridPenalty = Number(row?.gridPenalty); qualiGridDiv.textContent = qualiGridPosition > 0 ? `P${qualiGridPosition}` : "-"; if (gridPenalty > 0) qualiGridDiv.classList.add("penalty"); if (!isQualiSession || !hasQualiGrid) qualiGridDiv.classList.add("hidden"); sessionResultRow.appendChild(qualiGridDiv); const pointsDiv = document.createElement("div"); pointsDiv.className = "session-results-points"; pointsDiv.classList.add("session-results-cell"); const pts = Number(row?.points); if (pts > 0) { pointsDiv.textContent = `+${pts}`; pointsDiv.classList.add("positive"); } else { pointsDiv.textContent = String(pts); } sessionResultRow.appendChild(pointsDiv); if (!showPointsColumn) pointsDiv.classList.add("hidden"); if (isEditRace) pointsDiv.dataset.static = "0"; const container = document.querySelector(".session-results-table .session-results-rows"); if (container) container.appendChild(sessionResultRow); }); const rowsContainer = document.querySelector(".session-results-table .session-results-rows"); if (rowsContainer && !rowsContainer.dataset.sessionResultsDnDInit) { rowsContainer.dataset.sessionResultsDnDInit = "1"; rowsContainer.addEventListener("dragover", (e) => { if (!sessionResultsEditMode) return; if (!sessionResultsDraggedRow) return; e.preventDefault(); sessionResultsDragY = e.clientY; if (sessionResultsDragRaf) return; sessionResultsDragRaf = requestAnimationFrame(() => { sessionResultsDragRaf = 0; if (!sessionResultsDraggedRow) return; const afterEl = getDragAfterSessionResultsRow(rowsContainer, sessionResultsDragY); if (afterEl == null) { if (sessionResultsDraggedRow.nextSibling) rowsContainer.appendChild(sessionResultsDraggedRow); } else if (afterEl !== sessionResultsDraggedRow) { if (afterEl.previousSibling !== sessionResultsDraggedRow) { rowsContainer.insertBefore(sessionResultsDraggedRow, afterEl); } } }); }); } if (isEditRace && rowsContainer) { reindexRaceEditPositions(rowsContainer); const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); if (sessionKeyLower === "race") { ensureSessionResultsPointsInfo().finally(() => updateRaceEditPoints()); } updateRaceEditPoints(); manageSaveButton(true, "custom", saveSessionResultsRaceEdits); } const footer = document.querySelector(".session-results-footer"); if (!footer) return; footer.innerHTML = ""; footer.classList.add("d-none"); const trackId = Number(meta?.trackId); const trackCode = races_names?.[trackId]; const trackName = trackCode ? countries_data?.[String(trackCode)]?.track : ""; let fastestTime = null; let fastestRow = null; if (isQualiSession) { results.forEach((r) => { const times = [Number(r?.q1FastestLap), Number(r?.q2FastestLap), Number(r?.q3FastestLap)] .filter((v) => v > 0); const best = times.length ? Math.min(...times) : null; if (best != null && (fastestTime == null || best < fastestTime)) { fastestTime = best; fastestRow = r; } }); } else if (fastestLapBest != null) { fastestTime = fastestLapBest; fastestRow = results.find((r) => { const fl = Number(r?.fastestLap); return fl > 0 && Math.abs(fl - fastestLapBest) < 1e-6; }) || null; } if (!fastestRow || fastestTime == null) return; const labelDiv = document.createElement("div"); labelDiv.className = "session-results-footer-label"; labelDiv.textContent = "Fastest Lap"; footer.appendChild(labelDiv); const footerDriverDiv = document.createElement("div"); footerDriverDiv.className = "session-results-driver-name"; const footerDriverName = String(fastestRow?.name ?? ""); const footerNameParts = footerDriverName.split(" "); const footerNameSpan = document.createElement("span"); footerNameSpan.className = "session-results-first-name"; const footerSurnameSpan = document.createElement("span"); footerSurnameSpan.className = "bold-font session-results-surname"; format_name(footerDriverName, footerNameParts, footerNameSpan, footerSurnameSpan); footerSurnameSpan.textContent = String(footerSurnameSpan.textContent || "").toUpperCase(); footerDriverDiv.appendChild(footerNameSpan); footerDriverDiv.appendChild(footerSurnameSpan); footer.appendChild(footerDriverDiv); const footerTeamDiv = document.createElement("div"); footerTeamDiv.className = "session-results-team"; const footerTeamId = Number(fastestRow?.teamId ?? -1); const footerLogoDiv = footerTeamId !== -1 ? buildDriverLogoDiv(footerTeamId, { isF1: true, wrapperClass: "drivers-table-logo-div session-results-driver-logo-div" }) : (() => { const empty = document.createElement("div"); empty.className = "drivers-table-logo-div session-results-driver-logo-div"; return empty; })(); footerTeamDiv.appendChild(footerLogoDiv); const footerTeamNameSpan = document.createElement("span"); footerTeamNameSpan.className = "session-results-team-name"; footerTeamNameSpan.textContent = (footerTeamId !== -1) ? formatTeamNameForDisplay((combined_dict?.[footerTeamId] ?? ""), { upper: true }) : ""; footerTeamDiv.appendChild(footerTeamNameSpan); const footerEngineNameSpan = document.createElement("span"); footerEngineNameSpan.className = "session-results-engine-name"; if (currentFormula === 1 && footerTeamId !== -1) { const engineId = engine_allocations?.[footerTeamId]; const engineName = engineId != null ? engine_names?.[engineId] : ""; footerEngineNameSpan.textContent = String(engineName || "").toUpperCase(); } footerTeamDiv.appendChild(footerEngineNameSpan); footer.appendChild(footerTeamDiv); const rightDiv = document.createElement("div"); rightDiv.className = "session-results-footer-right"; const timeDiv = document.createElement("div"); timeDiv.className = "session-results-footer-time"; timeDiv.textContent = formatLapTime(fastestTime); footer.appendChild(timeDiv); const trackDiv = document.createElement("div"); trackDiv.className = "session-results-footer-track"; trackDiv.textContent = String(trackName || ""); trackDiv.classList.toggle("d-none", !trackName); rightDiv.appendChild(trackDiv); footer.appendChild(rightDiv); footer.classList.remove("d-none"); } function getDragAfterSessionResultsRow(container, y) { const rows = [...container.querySelectorAll(".session-results-row:not(.dragging)")]; return rows.reduce((closest, child) => { const box = child.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > closest.offset) { return { offset, element: child }; } return closest; }, { offset: Number.NEGATIVE_INFINITY, element: null }).element; } function reindexRaceEditPositions(container) { const rows = Array.from(container.querySelectorAll(".session-results-row")); rows.forEach((rowEl, i) => { const posDiv = rowEl.querySelector(".session-results-position"); if (!posDiv) return; const pos = i + 1; posDiv.textContent = String(pos); posDiv.classList.toggle("champion", pos === 1); posDiv.classList.toggle("second", pos === 2); posDiv.classList.toggle("third", pos === 3); rowEl.classList.toggle("leader", pos === 1); }); } function parseSessionResultsTimeToSeconds(txt) { const s = String(txt || "").trim(); if (!s || s === "-") return null; if (/^dnf$/i.test(s)) return null; const parts = s.split(":").map(p => p.trim()); if (parts.length === 3) { const h = Number(parts[0]); const m = Number(parts[1]); const sec = Number(parts[2]); return h * 3600 + m * 60 + sec; } if (parts.length === 2) { const m = Number(parts[0]); const sec = Number(parts[1]); return m * 60 + sec; } const sec = Number(parts[0]); return sec; } async function saveSessionResultsRaceEdits() { if (!sessionResultsEditMode || !sessionResultsLastFetched) return; const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); const raceId = Number(sessionResultsLastFetched?.raceId ?? meta?.raceId); const year = String(sessionResultsLastFetched?.year ?? "").trim(); const original = Array.isArray(sessionResultsLastFetched?.results) ? sessionResultsLastFetched.results : []; const timeByDriver = new Map(original.map(r => [String(r?.driverId), Number(r?.time)])); const dnfByDriver = new Map(original.map(r => [String(r?.driverId), Number(r?.dnf) === 1 ? 1 : 0])); const container = document.querySelector(".session-results-table .session-results-rows"); if (!container) return; const rows = Array.from(container.querySelectorAll(".session-results-row")); const edits = []; for (let i = 0; i < rows.length; i++) { const rowEl = rows[i]; const driverId = Number(rowEl.dataset.driverid); const input = rowEl.querySelector(".session-results-time-input"); const txt = input ? input.value : ""; const isDnf = /^dnf$/i.test(String(txt || "").trim()); let dnf = isDnf ? 1 : 0; let time = isDnf ? 0 : parseSessionResultsTimeToSeconds(txt); if (time == null && !isDnf) { const orig = timeByDriver.get(String(driverId)); time = orig; dnf = dnfByDriver.get(String(driverId)) ?? 0; } edits.push({ driverId, finishingPos: i + 1, time, dnf }); } try { const isSprint = sessionKeyLower === "sprintrace"; const resp = await new Command("editRaceResults", { raceId, edits, isSprint, sessionKey: sessionKeyLower }).promiseExecute(); await updateFront(resp); sessionResultsEditMode = false; manageSaveButton(false); new Command("sessionResultsRequest", { year, gameYear: game_version, raceId, sessionKey: sessionKeyLower }).execute(); } catch (e) { new_update_notifications("Failed to save race results", "error"); console.error(e); } } async function ensureSessionResultsPointsInfo() { if (sessionResultsPointsInfo) return sessionResultsPointsInfo; if (sessionResultsPointsInfoPromise) return sessionResultsPointsInfoPromise; sessionResultsPointsInfoPromise = new Command("pointsRegulationsRequest", {}).promiseExecute() .then((resp) => { sessionResultsPointsInfo = resp?.content ?? null; return sessionResultsPointsInfo; }) .catch((e) => { console.error(e); return null; }) .finally(() => { sessionResultsPointsInfoPromise = null; }); return sessionResultsPointsInfoPromise; } function updateRaceEditPoints() { if (!sessionResultsEditMode || !sessionResultsLastFetched) return; const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); if (sessionKeyLower !== "race" && sessionKeyLower !== "sprintrace") return; const container = document.querySelector(".session-results-table .session-results-rows"); if (!container) return; let posToPts = new Map(); let doublePoints = false; let flBonusEnabled = false; const original = Array.isArray(sessionResultsLastFetched?.results) ? sessionResultsLastFetched.results : []; const flByDriver = new Map(original.map(r => [Number(r?.driverId), Number(r?.fastestLap)])); if (sessionKeyLower === "race") { const pointsInfo = sessionResultsPointsInfo; if (!pointsInfo || !Array.isArray(pointsInfo.positionAndPoints)) return; const raceId = Number(sessionResultsLastFetched?.raceId ?? meta?.raceId); const yearKey = String(sessionResultsLastFetched?.year ?? "").trim(); const events = sessionResultsEventsCache.get(yearKey); const lastRaceId = Array.isArray(events) && events.length ? Math.max(...events.map(e => Number(e?.[0]))) : null; doublePoints = Number(pointsInfo?.isLastraceDouble) === 1 && lastRaceId != null && Number(raceId) === Number(lastRaceId); flBonusEnabled = Number(pointsInfo?.fastestLapBonusPoint) === 1; posToPts = new Map(pointsInfo.positionAndPoints .map(r => [Number(r?.[0]), Number(r?.[1])]) ); } else { // Sprint: keep the same points distribution as the original sprint results (pos -> points) original.forEach((r) => { const pos = Number(r?.pos); const pts = Number(r?.points); const dnf = Number(r?.dnf) === 1; if (!dnf && pos > 0 && pos !== 99) posToPts.set(pos, Math.max(0, pts)); }); } let fastestDriverId = null; let fastestLap = null; for (const [driverId, fl] of flByDriver.entries()) { if (fl > 0 && (fastestLap == null || fl < fastestLap)) { fastestLap = fl; fastestDriverId = driverId; } } const rows = Array.from(container.querySelectorAll(".session-results-row")); rows.forEach((rowEl, idx) => { const pos = idx + 1; const driverId = Number(rowEl.dataset.driverid); const dnf = Number(rowEl.dataset.dnf) === 1; let pts = 0; if (!dnf) { pts = Number(posToPts.get(pos) ?? 0); if (sessionKeyLower === "race" && flBonusEnabled && fastestDriverId != null && driverId === fastestDriverId && pos <= 10 && pts > 0) { pts += 1; } if (doublePoints) pts *= 2; } const pointsDiv = rowEl.querySelector(".session-results-points"); if (!pointsDiv) return; pointsDiv.classList.remove("positive"); pointsDiv.textContent = String(pts); if (pts > 0) { pointsDiv.textContent = `+${pts}`; pointsDiv.classList.add("positive"); } }); } function setupSessionResultsEditToggle() { const btn = document.getElementById("sessionResultsEditToggle"); if (!btn || btn.dataset.init) return; btn.dataset.init = "1"; btn.addEventListener("click", () => { if (!sessionResultsLastFetched) return; const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); if (sessionKeyLower !== "race" && sessionKeyLower !== "sprintrace") return; sessionResultsEditMode = !sessionResultsEditMode; if (!sessionResultsEditMode) { manageSaveButton(false); } onSessionResultsFetched(sessionResultsLastFetched); }); } function setupSessionResultsCompactToggle() { const btn = document.getElementById("sessionResultsCompactToggle"); if (!btn || btn.dataset.init) return; btn.dataset.init = "1"; btn.addEventListener("click", () => { sessionResultsCompactMode = !sessionResultsCompactMode; btn.querySelector("span").textContent = sessionResultsCompactMode ? "Expand" : "Compact"; btn.querySelector("i").className = sessionResultsCompactMode ? "bi bi-arrows-expand-vertical" : "bi bi-arrows-collapse-vertical"; const table = document.querySelector(".session-results-table"); if (table) table.classList.toggle("is-compact", sessionResultsCompactMode); }); } function safeJsonDownload(filename, jsonObj) { try { const content = JSON.stringify(jsonObj, null, 2); const blob = new Blob([content], { type: "application/json;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } catch (e) { console.error(e); if (typeof new_update_notifications === "function") { new_update_notifications("Failed to export JSON", "error"); } } } function slugifyForFilename(txt) { const s = String(txt || "").trim().toLowerCase(); return s .replace(/grand prix/gi, "gp") .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, "") .slice(0, 80) || "unknown"; } function slugifyForUniqueName(txt) { const s = String(txt || "").trim().toLowerCase(); return s.replace(/[^a-z0-9]+/g, "").slice(0, 40) || "unknown"; } function normalizeRltoolsDriverName(name) { return String(name || "").replace(/\bAndreaKimi\b/g, "Kimi").trim(); } function getRltoolsTrackUniqueName(trackId, year) { const trackCode = races_names?.[Number(trackId)]; const rlToolsName = trackCode ? String(countries_data?.[String(trackCode)]?.rlToolsName || "").trim() : ""; if (rlToolsName) return rlToolsName; const trackName = getGpDisplayName(trackId); return `${slugifyForUniqueName(trackName)}.${year || "0"}`; } function getQualifyingStageNumber(qualifyingStage) { return Number(qualifyingStage?.fileSuffix) || 0; } function getSessionPositionForExport(sessionKeyLower, weekendType, qualifyingStage = null) { const isSprintWeekend = Number(weekendType) === 1; const qualiStageNumber = getQualifyingStageNumber(qualifyingStage); if (sessionKeyLower === "fp" || sessionKeyLower === "fp1" || sessionKeyLower === "fp2" || sessionKeyLower === "fp3") { return 0; } if (sessionKeyLower === "sprintquali") { return qualiStageNumber || 0; } if (sessionKeyLower === "sprintrace") { return 4; } if (sessionKeyLower === "quali") { if (isSprintWeekend) { return qualiStageNumber > 0 ? qualiStageNumber + 3 : 4; } return qualiStageNumber || 0; } if (sessionKeyLower === "race") { return isSprintWeekend ? 7 : 4; } return 0; } function getRltoolsSessionType(sessionKeyLower) { if (sessionKeyLower === "race" || sessionKeyLower === "sprintrace") return "Race"; if (sessionKeyLower === "quali" || sessionKeyLower === "sprintquali") return "Qualification"; if (sessionKeyLower === "fp" || sessionKeyLower === "fp1" || sessionKeyLower === "fp2" || sessionKeyLower === "fp3") return "Practice"; return "Unknown"; } function getRltoolsSessionFilenamePart(sessionKeyLower) { if (sessionKeyLower === "quali") return "qualification"; if (sessionKeyLower === "sprintquali") return "sprint_qualification"; if (sessionKeyLower === "fp" || sessionKeyLower === "fp1") return "practice_1"; if (sessionKeyLower === "fp2") return "practice_2"; if (sessionKeyLower === "fp3") return "practice_3"; if (sessionKeyLower === "sprintrace") return "sprint_race"; if (sessionKeyLower === "race") return "race"; return slugifyForFilename(sessionKeyLower); } function getRltoolsQualifyingStageConfigs() { return [ { lapKey: "q1FastestLap", qualType: "Q1", fileSuffix: "1" }, { lapKey: "q2FastestLap", qualType: "Q2", fileSuffix: "2" }, { lapKey: "q3FastestLap", qualType: "Q3", fileSuffix: "3" } ]; } function secondsToIntMs(v) { const n = Number(v); if (!Number.isFinite(n) || n <= 0) return 0; return Math.round(n * 1000); } function getShownSessionResultsRowsForExport() { if (!sessionResultsLastFetched) return []; const meta = sessionResultsLastFetched?.meta || {}; const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); const original = Array.isArray(sessionResultsLastFetched?.results) ? sessionResultsLastFetched.results : []; if (!sessionResultsEditMode || (sessionKeyLower !== "race" && sessionKeyLower !== "sprintrace")) { return original.map(r => ({ ...r })); } const container = document.querySelector(".session-results-table .session-results-rows"); if (!container) return original.map(r => ({ ...r })); const byDriverId = new Map(original.map(r => [Number(r?.driverId), r])); const timeByDriver = new Map(original.map(r => [String(r?.driverId), Number(r?.time)])); const dnfByDriver = new Map(original.map(r => [String(r?.driverId), Number(r?.dnf) === 1 ? 1 : 0])); const rows = Array.from(container.querySelectorAll(".session-results-row")); const merged = []; for (let i = 0; i < rows.length; i++) { const rowEl = rows[i]; const driverId = Number(rowEl.dataset.driverid); const base = byDriverId.get(driverId) || { driverId }; const input = rowEl.querySelector(".session-results-time-input"); const txt = input ? input.value : ""; const isDnf = /^dnf$/i.test(String(txt || "").trim()); let dnf = isDnf ? 1 : 0; let time = isDnf ? 0 : parseSessionResultsTimeToSeconds(txt); if (time == null && !isDnf) { const orig = timeByDriver.get(String(driverId)); time = orig; dnf = dnfByDriver.get(String(driverId)) ?? 0; } merged.push({ ...base, pos: i + 1, time, dnf }); } return merged; } function buildRltoolsExportObject(options = {}) { if (!sessionResultsLastFetched) return null; const meta = sessionResultsLastFetched?.meta || {}; const year = String(sessionResultsLastFetched?.year ?? "").trim(); const trackId = Number(meta?.trackId); const weekendType = Number(meta?.weekendType); const trackName = getGpDisplayName(trackId); const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase(); const sessionType = getRltoolsSessionType(sessionKeyLower); const qualifyingStage = sessionType === "Qualification" ? options.qualifyingStage || null : null; const sessionPosition = getSessionPositionForExport(sessionKeyLower, weekendType, qualifyingStage); const stageLapKey = qualifyingStage?.lapKey || ""; const raceType = "Regular"; const qualType = qualifyingStage?.qualType || "Regular"; const rows = getShownSessionResultsRowsForExport(); const allResults = Array.isArray(rows) ? rows : []; const results = qualifyingStage ? allResults .filter((r) => Number(r?.[stageLapKey]) > 0) .sort((a, b) => { const aLap = Number(a?.[stageLapKey]) || 0; const bLap = Number(b?.[stageLapKey]) || 0; if (aLap !== bLap) return aLap - bLap; return (Number(a?.pos) || 0) - (Number(b?.pos) || 0); }) : allResults; if (!results.length) return null; const stagePositions = new Map(); if (qualifyingStage) { results .forEach((r, idx) => { stagePositions.set(Number(r?.driverId), idx + 1); }); } const bestLapByRow = (r) => { if (sessionType === "Race") return Number(r?.fastestLap) || 0; if (qualifyingStage) return Number(r?.[stageLapKey]) || 0; if (sessionType === "Qualification") { const times = [Number(r?.q1FastestLap), Number(r?.q2FastestLap), Number(r?.q3FastestLap)].filter(v => v > 0); return times.length ? Math.min(...times) : 0; } return Number(r?.fastestLap) || Number(r?.time) || 0; }; const leaderRow = results[0] || null; const leaderTimeSec = (() => { if (!leaderRow) return 0; if (sessionType === "Race") return Number(leaderRow?.time) || 0; if (sessionType === "Qualification") return bestLapByRow(leaderRow); return Number(leaderRow?.time) || Number(leaderRow?.fastestLap) || 0; })(); const leaderTimeInt = secondsToIntMs(leaderTimeSec); let fastestLapTimeSec = 0; let fastestLapDriverName = ""; for (const r of results) { const t = bestLapByRow(r); if (t > 0 && (fastestLapTimeSec <= 0 || t < fastestLapTimeSec)) { fastestLapTimeSec = t; fastestLapDriverName = normalizeRltoolsDriverName(r?.name); } } const dotdDriverId = Number(meta?.dotdDriverId); const dotdName = (sessionKeyLower === "race" && dotdDriverId > 0) ? normalizeRltoolsDriverName(results.find(r => Number(r?.driverId) === dotdDriverId)?.name) : ""; const drivers = results.map((r) => { const driverName = normalizeRltoolsDriverName(r?.name); const teamId = Number(r?.teamId ?? -1); const teamName = (teamId !== -1) ? String(combined_dict?.[teamId] ?? "") : ""; const teamUnique = teamName ? `${slugifyForUniqueName(teamName)}.${year || "0"}` : ""; const lapsCount = Number(r?.laps) || 0; const bestLapTimeInt = secondsToIntMs(bestLapByRow(r)); let timeSec = 0; if (sessionType === "Race") timeSec = Number(r?.time) || 0; else if (sessionType === "Qualification") timeSec = 0; else timeSec = Number(r?.time) || Number(r?.fastestLap) || 0; const timeInt = secondsToIntMs(timeSec); const gapInt = sessionType === "Race" && leaderTimeInt > 0 && timeInt > 0 && Number(r?.pos) !== 1 ? Math.max(0, timeInt - leaderTimeInt) : 0; const status = sessionType === "Race" ? ((timeInt > 0 && Number(r?.dnf) !== 1) ? "Ok" : "Dnf") : ((bestLapTimeInt > 0 || lapsCount > 0) ? "Ok" : "Dns"); const base = { Driver: { Name: driverName }, RaceNumber: Number(r?.raceNumber) || 0, Position: qualifyingStage ? (stagePositions.get(Number(r?.driverId)) || 0) : (Number(r?.pos) || 0), Team: { Name: teamName, UniqueName: teamUnique }, SeatType: "Primary", Status: status, TimeInt: timeInt, GapInt: gapInt }; if (sessionType === "Race") { base.FastestLapTimeInt = secondsToIntMs(Number(r?.fastestLap) || 0); base.LapsCount = lapsCount; base.GridPosition = Number(r?.grid) || 0; } else { base.TimeInt = 0; base.GapInt = 0; base.FastestLapTimeInt = bestLapTimeInt; base.LapsCount = lapsCount; } return base; }); const out = { SessionType: sessionType, RaceType: raceType, QualType: qualType, SessionPosition: sessionPosition, Date: null, TrackName: trackName, TrackUniqueName: getRltoolsTrackUniqueName(trackId, year), Drivers: drivers }; if (fastestLapTimeSec > 0 && fastestLapDriverName) { out.FastestLapDriver = { Name: fastestLapDriverName }; out.FastestLapTimeInt = secondsToIntMs(fastestLapTimeSec); } if (dotdName) { out.DriverDayDriver = { Name: dotdName }; } return out; } function exportShownSessionResultsToRltools() { if (!sessionResultsLastFetched) { if (typeof new_update_notifications === "function") { new_update_notifications("No session results to export", "error"); } return; } const meta = sessionResultsLastFetched?.meta || {}; const year = String(sessionResultsLastFetched?.year ?? "").trim() || "unknown_year"; const trackName = getGpDisplayName(meta?.trackId); const sessionKeyLower = String(sessionResultsLastFetched?.sessionKey ?? meta?.sessionKey ?? "").toLowerCase() || "unknown_session"; const sessionSlug = getRltoolsSessionFilenamePart(sessionKeyLower); const exports = []; if (sessionKeyLower === "quali" || sessionKeyLower === "sprintquali") { getRltoolsQualifyingStageConfigs().forEach((stage) => { const payload = buildRltoolsExportObject({ qualifyingStage: stage }); if (!payload) return; exports.push({ filename: `results_${slugifyForFilename(trackName)}_${slugifyForFilename(year)}_${slugifyForFilename(sessionSlug)}${stage.fileSuffix}.json`, payload }); }); } else { const payload = buildRltoolsExportObject(); if (!payload) return; exports.push({ filename: `results_${slugifyForFilename(trackName)}_${slugifyForFilename(year)}_${slugifyForFilename(sessionSlug)}.json`, payload }); } if (!exports.length) return; exports.forEach(({ filename, payload }) => { safeJsonDownload(filename, payload); }); if (typeof new_update_notifications === "function") { if (exports.length === 1) { new_update_notifications(`Exported ${exports[0].filename}`, "success"); } else { new_update_notifications(`Exported ${exports.length} files`, "success"); } } } function setupSessionResultsExportRltoolsButton() { const compactToggle = document.getElementById("sessionResultsCompactToggle"); if (!compactToggle) return; const existing = document.getElementById("sessionResultsExportRltoolsButton"); if (existing) return; const btn = document.createElement("div"); btn.className = "button-with-icon d-none"; btn.id = "sessionResultsExportRltoolsButton"; const icon = document.createElement("i"); icon.className = "bi bi-box-arrow-up"; btn.appendChild(icon); const span = document.createElement("span"); span.textContent = "RLtools"; btn.appendChild(span); btn.addEventListener("click", exportShownSessionResultsToRltools); compactToggle.parentNode.insertBefore(btn, compactToggle); } setupSessionResultsEditToggle(); setupSessionResultsCompactToggle(); setupSessionResultsExportRltoolsButton(); //time comes in seconds.miliseconds, and I want it in hh:mm:ss.sss format (if no hh, then mm:ss.sss) function formatLapTime(lapTimeMs) { const totalMs = Math.floor(lapTimeMs * 1000); const hours = Math.floor(totalMs / 3600000); const minutes = Math.floor((totalMs % 3600000) / 60000); const seconds = Math.floor((totalMs % 60000) / 1000); const milliseconds = totalMs % 1000; //if it starts by a 0, remove it if (hours > 0) { return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(milliseconds).padStart(3, "0")}`; } else { return `${minutes}:${String(seconds).padStart(2, "0")}.${String(milliseconds).padStart(3, "0")}`; } } function formatGapTime(lapTimeMs) { const totalMs = Math.floor(lapTimeMs * 1000); const hours = Math.floor(totalMs / 3600000); const minutes = Math.floor((totalMs % 3600000) / 60000); const seconds = Math.floor((totalMs % 60000) / 1000); const milliseconds = totalMs % 1000; if (hours > 0) { return `${hours}:${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}.${String(milliseconds).padStart(3, "0")}`; } if (minutes > 0) { return `${minutes}:${String(seconds).padStart(2, "0")}.${String(milliseconds).padStart(3, "0")}`; } return `${seconds}.${String(milliseconds).padStart(3, "0")}`; } function getGpDisplayName(trackId) { const key = races_names?.[Number(trackId)]; return names_full?.[key] || key || `Track ${trackId}`; } function getSessionOptionsForWeekend(weekendType) { const isSprint = Number(weekendType) === 1; if (isSprint) { return [ { key: "fp", label: "Free Practice" }, { key: "sprintquali", label: "Sprint Quali" }, { key: "sprintrace", label: "Sprint Race" }, { key: "quali", label: "Quali" }, { key: "race", label: "Race" } ]; } return [ { key: "fp1", label: "Free Practice 1" }, { key: "fp2", label: "Free Practice 2" }, { key: "fp3", label: "Free Practice 3" }, { key: "quali", label: "Qualifying" }, { key: "race", label: "Race" } ]; } function getLatestYearFromMenu() { const yearMenu = document.getElementById("yearMenu"); const years = yearMenu ? Array.from(yearMenu.querySelectorAll("a")) .map(a => a.dataset.year) .filter(year => /^\d+$/.test(year)) .map(Number) : []; return years.length ? Math.max(...years) : null; } function openSessionResultsForRace(year, raceId, sessionKey) { const recordsButton = document.getElementById("recordsTypeButton"); if (recordsButton) { recordsButton.querySelector("span.dropdown-label").textContent = "Session Results"; recordsButton.dataset.value = "sessionresults"; } syncRecordsTypeDropdownChecks(); updateTopPanelControlsVisibility(); showSessionResultsTable(); new Command("sessionResultsRequest", { year, gameYear: game_version, raceId, sessionKey }).execute(); } async function ensureSessionResultsMenuPopulated() { const gpMenu = document.getElementById("sessionResultsGpMenu"); if (!gpMenu) return; const gpList = document.getElementById("sessionResultsGpList"); const sessionMenu = document.getElementById("sessionResultsSessionMenu"); if (!gpList || !sessionMenu) return; const yearBtn = document.getElementById("yearButton"); const selectedYear = yearBtn?.dataset?.year; if (!selectedYear || selectedYear === "all") { gpList.innerHTML = ""; sessionMenu.classList.remove("is-open"); sessionMenu.innerHTML = ""; sessionResultsActiveGpAnchor = null; sessionResultsActiveGpMeta = null; const item = document.createElement("a"); item.className = "redesigned-dropdown-item"; item.textContent = "Select a Year"; gpList.appendChild(item); return; } gpList.innerHTML = ""; sessionMenu.classList.remove("is-open"); sessionMenu.innerHTML = ""; sessionResultsActiveGpAnchor = null; sessionResultsActiveGpMeta = null; const loading = document.createElement("a"); loading.className = "redesigned-dropdown-item"; loading.textContent = "Loading..."; gpList.appendChild(loading); try { let events = sessionResultsEventsCache.get(selectedYear); if (!Array.isArray(events)) { const resp = await new Command("eventsFromRequest", { year: selectedYear, formula: 1 }).promiseExecute(); events = resp?.content?.events || []; sessionResultsEventsCache.set(selectedYear, events); } gpList.innerHTML = ""; const doneEvents = (Array.isArray(events) ? events : []).filter((e) => { const state = Number(e?.[3]); return state === 1 || state === 2; }); doneEvents.forEach((evt) => { const raceId = evt?.[0]; const trackId = evt?.[1]; const weekendType = evt?.[2]; const trigger = document.createElement("a"); trigger.className = "redesigned-dropdown-item"; trigger.style.cursor = "pointer"; const label = document.createElement("span"); label.textContent = getGpDisplayName(trackId); const chevron = document.createElement("i"); chevron.className = "bi bi-chevron-right"; trigger.appendChild(label); trigger.appendChild(chevron); trigger.addEventListener("mouseenter", () => { sessionResultsActiveGpAnchor = trigger; sessionResultsActiveGpMeta = { year: selectedYear, raceId, weekendType, trackId }; populateAndShowSessionResultsSessionMenu(); }); trigger.addEventListener("click", () => { sessionResultsActiveGpAnchor = trigger; sessionResultsActiveGpMeta = { year: selectedYear, raceId, weekendType, trackId }; openSessionResultsForRace(selectedYear, raceId, "race"); }); gpList.appendChild(trigger); }); if (doneEvents.length === 0) { const item = document.createElement("a"); item.className = "redesigned-dropdown-item"; item.textContent = "No completed races"; gpList.appendChild(item); } if (!gpMenu.dataset.sessionResultsSessionMenuInit) { gpMenu.dataset.sessionResultsSessionMenuInit = "1"; const reposition = () => populateAndShowSessionResultsSessionMenu({ repositionOnly: true }); document.getElementById("sessionResultsGpList")?.addEventListener("scroll", reposition); gpMenu.addEventListener("mouseleave", () => { sessionMenu.classList.remove("is-open"); sessionResultsActiveGpAnchor = null; sessionResultsActiveGpMeta = null; }); } } catch (e) { gpList.innerHTML = ""; const item = document.createElement("a"); item.className = "redesigned-dropdown-item"; item.textContent = "Failed to load events"; gpList.appendChild(item); console.error(e); } } function populateAndShowSessionResultsSessionMenu(opts = {}) { const sessionMenu = document.getElementById("sessionResultsSessionMenu"); const gpMenu = document.getElementById("sessionResultsGpMenu"); const gpList = document.getElementById("sessionResultsGpList"); if (!sessionMenu || !gpMenu || !gpList) return; if (!sessionResultsActiveGpAnchor || !sessionResultsActiveGpMeta) { sessionMenu.classList.remove("is-open"); return; } const { year, raceId, weekendType } = sessionResultsActiveGpMeta; if (!opts.repositionOnly) { sessionMenu.innerHTML = ""; const optionsAll = getSessionOptionsForWeekend(weekendType); const latestYear = getLatestYearFromMenu(); const isLatestYear = latestYear == null ? true : Number(year) === Number(latestYear); const options = isLatestYear ? optionsAll : optionsAll.filter((o) => o.key === "race" || (o.key === "sprintrace" && Number(weekendType) === 1)); options.forEach((opt) => { const a = document.createElement("a"); a.className = "redesigned-dropdown-item"; a.style.cursor = "pointer"; a.textContent = opt.label; a.addEventListener("click", () => { openSessionResultsForRace(year, raceId, opt.key); }); sessionMenu.appendChild(a); }); } // Position the session menu aligned with the hovered GP row (inside the scroll container) const top = sessionResultsActiveGpAnchor.offsetTop - gpList.scrollTop; sessionMenu.style.top = `${Math.max(0, top)}px`; sessionMenu.classList.add("is-open"); } function setYearButton(el) { const yearBtn = document.getElementById("yearButton"); yearBtn.querySelector("span.dropdown-label").textContent = el.textContent.trim(); yearBtn.dataset.year = el.dataset.year; // <- guardamos el valor syncYearDropdownChecks() } export function loadRecordsList(data) { const recordsList = document.querySelector(".records-list") recordsList.innerHTML = "" let visibleIndex = 0; const hideHistoric = document.querySelector(".hide-historic-drivers").classList.contains("active"); data.forEach(function (record, index) { if (record.value <= 0) return; let recordDiv = document.createElement("div") recordDiv.classList = "record-item" if (record.teamId !== -1) { recordDiv.classList.add(`${team_dict[record.teamId]}-record`) } else { recordDiv.classList.add("generic-record") } const isHistoric = record.id === -1; if (isHistoric) recordDiv.classList.add("historic-driver"); const shouldHide = isHistoric && hideHistoric; if (shouldHide) recordDiv.classList.add("d-none"); const number = document.createElement("div"); number.className = "record-number"; if (!shouldHide) { number.textContent = `${++visibleIndex}.`; } else { number.textContent = ""; } let recordName = document.createElement("div") recordName.classList = "record-name" let fullName = news_insert_space(record.name) let surname = fullName.split(" ").pop() let nameSpan = document.createElement("span") nameSpan.textContent = fullName.replace(surname, "") let surnameSpan = document.createElement("span") surnameSpan.textContent = surname surnameSpan.classList = "bold-font record-surname" recordName.appendChild(nameSpan) recordName.appendChild(surnameSpan) let numberAndName = document.createElement("div") numberAndName.classList = "number-and-name" let nameAndTeam = document.createElement("div") nameAndTeam.classList = "name-and-team" let teamDiv = document.createElement("div") teamDiv.classList = "record-team" teamDiv.textContent = record.teamId !== -1 ? formatTeamNameForDisplay(combined_dict[record.teamId]) : record.retired === 1 ? "Retired" : "N/A"; nameAndTeam.appendChild(recordName) nameAndTeam.appendChild(teamDiv) numberAndName.appendChild(number) const recordTeamId = Number(record.teamId ?? -1); if (recordTeamId !== -1) { const logoDiv = buildDriverLogoDiv(recordTeamId, { isF1: currentFormula === 1, wrapperClass: "drivers-table-logo-div record-logo-div" }); numberAndName.appendChild(logoDiv); } numberAndName.appendChild(nameAndTeam) let extraStatsSection = document.createElement("div") extraStatsSection.classList = "extra-stats-section" let totalStarts = document.createElement("div") totalStarts.classList = "extra-stat" totalStarts.textContent = `Races: ${record.totalStarts}` let percentageRate = document.createElement("div") percentageRate.classList = "extra-stat perecentage-rate" if (record.record === "wins" || record.record === "champs") { percentageRate.textContent = `Win Rate: ${(record.totalWins / record.totalStarts * 100).toFixed(2)}%` } else if (record.record === "podiums") { percentageRate.textContent = `Podium Rate: ${(record.totalPodiums / record.totalStarts * 100).toFixed(2)}%` } else if (record.record === "poles") { percentageRate.textContent = `Pole Rate: ${(record.totalPoles / record.totalStarts * 100).toFixed(2)}%` } else if (record.record === "fastestlaps") { percentageRate.textContent = `Fastest Lap Rate: ${(record.totalFastestLaps / record.totalStarts * 100).toFixed(2)}%` } if (record.record !== "races" && record.record !== "points" && record.record !== "dotd") { extraStatsSection.appendChild(percentageRate) } let firstRace = document.createElement("div") firstRace.classList = "extra-stat" let trackName = record.firstRace.trackName ? record.firstRace.trackName : (record.firstRace.trackId ? names_full[races_names[record.firstRace.trackId]] : "") firstRace.textContent = `First Race: ${trackName} ${record.firstRace.season}` let firstPodium = document.createElement("div") firstPodium.classList = "extra-stat" let podiumTrackName = record.firstPodium.trackName ? record.firstPodium.trackName : (record.firstPodium.trackId ? names_full[races_names[record.firstPodium.trackId]] : "") firstPodium.textContent = `First Podium: ${podiumTrackName} ${record.firstPodium.season}` let firstWin = document.createElement("div") firstWin.classList = "extra-stat" let winTrackName = record.firstWin.trackName ? record.firstWin.trackName : (record.firstWin.trackId ? names_full[races_names[record.firstWin.trackId]] : "") firstWin.textContent = `First Win: ${winTrackName} ${record.firstWin.season}` let lastWin = document.createElement("div") lastWin.classList = "extra-stat" let lastWinTrackName = record.lastWin.trackName ? record.lastWin.trackName : (record.lastWin.trackId ? names_full[races_names[record.lastWin.trackId]] : "") lastWin.textContent = `Last Win: ${lastWinTrackName} ${record.lastWin.season}` let fastestLaps = document.createElement("div") fastestLaps.classList = "extra-stat" fastestLaps.textContent = `Fastest Laps: ${record.totalFastestLaps}` let sprintWins = document.createElement("div") sprintWins.classList = "extra-stat" sprintWins.textContent = `Sprint Wins: ${record.totalSprintWins}` let poles = document.createElement("div") poles.classList = "extra-stat" poles.textContent = `Poles: ${record.totalPoles}` let podiums = document.createElement("div") podiums.classList = "extra-stat" podiums.textContent = `Podiums: ${record.totalPodiums}` let points = document.createElement("div") points.classList = "extra-stat" points.textContent = `Points: ${record.totalPointsScored}` let wins = document.createElement("div") wins.classList = "extra-stat" wins.textContent = `Wins: ${record.totalWins}` let champs = document.createElement("div") champs.classList = "extra-stat" champs.textContent = `WDCs: ${record.totalChampionshipWins}` if (document.querySelector("#yearButton").dataset.year === "all" && record.record !== "races") { extraStatsSection.appendChild(totalStarts) } if (record.record !== "points") { extraStatsSection.appendChild(points) } if (record.firstRace.season !== 0) { extraStatsSection.appendChild(firstRace) } if (record.firstPodium.season !== 0) { extraStatsSection.appendChild(firstPodium) } if (record.firstWin.season !== 0) { extraStatsSection.appendChild(firstWin) } if (record.lastWin.season !== 0) { extraStatsSection.appendChild(lastWin) } if (record.record !== "fastestlaps" && record.totalFastestLaps > 0) { extraStatsSection.appendChild(fastestLaps) } if (record.totalSprintWins > 0) { extraStatsSection.appendChild(sprintWins) } if (record.record !== "wins" && record.totalWins > 0) { extraStatsSection.appendChild(wins) } if (record.record !== "podiums" && record.totalPodiums > 0) { extraStatsSection.appendChild(podiums) } if (record.record !== "poles" && record.totalPoles > 0) { extraStatsSection.appendChild(poles) } if (record.record !== "champs" && record.totalChampionshipWins > 0) { extraStatsSection.appendChild(champs) } let totalPoints = document.createElement("div") totalPoints.classList = "extra-stat" totalPoints.textContent = `Points: ${record.totalPointsScored}` numberAndName.appendChild(extraStatsSection) let recordValue = document.createElement("div") recordValue.classList = "record-value" recordValue.textContent = record.value recordDiv.appendChild(numberAndName) recordDiv.appendChild(recordValue) recordsList.appendChild(recordDiv) }); } export function loadTeamRecordsList(payload) { const recordsList = document.querySelector(".records-list"); recordsList.innerHTML = ""; const data = Array.isArray(payload) ? payload : payload?.record; if (!Array.isArray(data) || data.length === 0) return; let visibleIndex = 0; const formatNameNoUppercase = (fullName, spanName, spanLastName) => { if (!fullName) return; if (fullName.length > 17) { let nameArray = fullName.split(" "); let firstName = nameArray[0]; if (insert_space(firstName).includes(" ")) { let splitName = insert_space(firstName).split(" "); spanName.textContent = splitName[0][0] + ". " + splitName[1] + " "; } else { spanName.textContent = firstName[0] + ". "; } spanLastName.textContent = nameArray.slice(1).join(" "); } else { const nameSplitted = fullName.split(" "); spanName.textContent = insert_space(nameSplitted[0]) + " "; spanLastName.textContent = nameSplitted.slice(1).join(" "); } }; const buildTeamDriverInline = (driver) => { const wrap = document.createElement("span"); wrap.className = "record-team-driver"; const num = driver?.number; if (num != null) { const numSpan = document.createElement("span"); numSpan.className = "record-driver-number"; numSpan.textContent = `#${num}`; wrap.appendChild(numSpan); } const name = String(driver?.name ?? "").trim(); if (name) { const parts = name.split(" "); const nameSpan = document.createElement("span"); const lastSpan = document.createElement("span"); formatNameNoUppercase(name, nameSpan, lastSpan); wrap.appendChild(nameSpan); wrap.appendChild(lastSpan); } return wrap; }; data.forEach((item) => { const teamId = Number(item?.teamId ?? item?.TeamID ?? -1); const value = Number(item?.value ?? 0); if (teamId === -1) return; if (value <= 0) return; const recordDiv = document.createElement("div"); recordDiv.classList = "record-item"; if (team_dict[teamId]) { recordDiv.classList.add(`${team_dict[teamId]}-record`); } else { recordDiv.classList.add("generic-record"); } const numberAndName = document.createElement("div"); numberAndName.classList = "number-and-name"; const number = document.createElement("div"); number.className = "record-number"; number.textContent = `${++visibleIndex}.`; const logoDiv = buildDriverLogoDiv(teamId, { isF1: true, wrapperClass: "drivers-table-logo-div record-logo-div" }); const nameAndTeam = document.createElement("div"); nameAndTeam.classList = "name-and-team"; const recordName = document.createElement("div"); recordName.classList = "record-name bold-font"; recordName.textContent = formatTeamNameForDisplay(combined_dict[teamId] || "", { upper: true }); nameAndTeam.appendChild(recordName); const driversObj = item?.drivers; const driver1 = driversObj?.driver1 ?? null; const driver2 = driversObj?.driver2 ?? null; if (driver1 || driver2) { const driversDiv = document.createElement("div"); driversDiv.classList = "record-team record-team-drivers"; const drivers = [driver1, driver2].filter(Boolean); drivers.forEach((driver, idx) => { if (idx > 0) { const sep = document.createElement("span"); sep.className = "record-drivers-separator"; sep.textContent = "|"; driversDiv.appendChild(sep); } driversDiv.appendChild(buildTeamDriverInline(driver)); }); nameAndTeam.appendChild(driversDiv); } numberAndName.appendChild(number); numberAndName.appendChild(logoDiv); numberAndName.appendChild(nameAndTeam); const breakdown = Array.isArray(item?.breakdown) ? item.breakdown : []; if (breakdown.length > 0) { const extraStatsSection = document.createElement("div"); extraStatsSection.classList = "extra-stats-section"; breakdown.forEach((d) => { if (!d || Number(d.count) <= 0) return; const statDiv = document.createElement("div"); statDiv.classList = "extra-stat"; const driverName = String(d.name ?? "").trim(); if (driverName) { const parts = driverName.split(" "); const nameSpan = document.createElement("span"); const lastSpan = document.createElement("span"); formatNameNoUppercase(driverName, nameSpan, lastSpan); statDiv.appendChild(nameSpan); statDiv.appendChild(lastSpan); } const countSpan = document.createElement("span"); countSpan.textContent = `: ${d.count}`; statDiv.appendChild(countSpan); extraStatsSection.appendChild(statDiv); }); numberAndName.appendChild(extraStatsSection); } const recordValue = document.createElement("div"); recordValue.classList = "record-value"; recordValue.textContent = String(value); recordDiv.appendChild(numberAndName); recordDiv.appendChild(recordValue); recordsList.appendChild(recordDiv); }); } document.querySelectorAll("#recordsTypeDropdown > a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector("#recordsTypeButton span").textContent = elem.textContent document.querySelector("#recordsTypeButton").dataset.value = elem.dataset.value syncRecordsTypeDropdownChecks() updateTopPanelControlsVisibility(); manageRecordsSelected(null) }) }) document.querySelector("#recordsTypeDropdown .session-results-root")?.addEventListener("mouseenter", () => { ensureSessionResultsMenuPopulated(); }); document.querySelector("#recordsTypeDropdown .session-results-root")?.addEventListener("click", () => { const recordsButton = document.getElementById("recordsTypeButton"); if (recordsButton) { recordsButton.querySelector("span.dropdown-label").textContent = "Session Results"; recordsButton.dataset.value = "sessionresults"; } syncRecordsTypeDropdownChecks(); updateTopPanelControlsVisibility(); manageRecordsSelected(null); }); document.querySelector(".hide-historic-drivers").addEventListener("click", function () { this.classList.toggle("active"); this.querySelector("span").textContent = this.classList.contains("active") ? "Show Historic Drivers" : "Hide Historic Drivers" const recordsList = document.querySelectorAll(".record-item"); // Ocultar/mostrar históricos recordsList.forEach(function (record) { if (record.classList.contains("historic-driver")) { record.classList.toggle("d-none"); } }); // Renumerar solo los visibles let visibleIndex = 1; recordsList.forEach(function (record) { if (!record.classList.contains("d-none")) { const numberEl = record.querySelector(".record-number"); if (numberEl) { numberEl.textContent = `${visibleIndex}.`; } visibleIndex++; } }); }); ================================================ FILE: src/js/frontend/stats.js ================================================ import { inverted_countries_abreviations } from "../backend/scriptUtils/countries"; import { team_dict, mentalityModifiers, teamOrder, mentality_dict, combined_dict, logos_disc } from "./config"; import { colors_dict } from "./head2head"; import { attachHold } from "./renderer"; import { insert_space, manageColor, format_name } from "./transfers"; import Chart from 'chart.js/auto'; let driverStatTitle = document.getElementById("driverStatsTitle") export let statPanelShown = 0; export let typeOverall = "driver"; export let typeEdit; let oldNum; let editStatsItems = []; let timer; let statsRadarChart = null; const clearIcon2 = document.querySelector("#filterContainer .bi-x"); let isComparisonModeActive = false; let firstDriverStats = null; let secondDriverStats = null; let numbersAvailable = []; const compareButton = document.getElementById('compareButton'); const addStaffButton = document.getElementById("addStaffButton"); const addStaffTypeDropdown = document.getElementById("addStaffTypeDropdown"); const draftStaffTypeControl = document.getElementById("draftStaffTypeControl"); const genderSwapButton = document.getElementById("genderSwapButton"); const nationalityButton = document.getElementById("nationalityButton"); const nationalityMenu = document.getElementById("nationalityMenu"); const plusBtn = document.querySelector('.age-holder .bi-plus'); const minusBtn = document.querySelector('.age-holder .bi-dash'); const ageSpan = document.querySelector('.age-holder .actual-age'); const plusR = document.querySelector('.retirement-age .bi-plus'); const minusR = document.querySelector('.retirement-age .bi-dash'); const inputR = document.querySelector('.retirement-age .actual-retirement'); let plusNumberBtn = document.querySelector('.number-buttons .bi-plus'); let minusNumberBtn = document.querySelector('.number-buttons .bi-dash'); let numberSpan = document.querySelector('.number-holder'); let selectedAddStaffType = "0"; let currentDraftId = null; const staffTypeConfig = { "0": { label: "Drivers", spaceStats: "driverStats" }, "1": { label: "Technical Chiefs", spaceStats: "chiefStats" }, "2": { label: "Race Engineers", spaceStats: "engineerStats" }, "3": { label: "H. of Aerodynamics", spaceStats: "aeroStats" }, "4": { label: "Sporting Directors", spaceStats: "directorStats" } }; function setAttributesTitle(typeStaff) { const config = staffTypeConfig[String(typeStaff ?? "0")] || staffTypeConfig["0"]; const titleText = document.getElementById("attributesTitleText"); if (titleText) { titleText.textContent = `${config.label} Attributes`; } } function initNationalityDropdown() { if (!nationalityMenu) return; nationalityMenu.innerHTML = ""; const entries = Object.entries(inverted_countries_abreviations || {}) .filter(([code, name]) => !!code && !!name) .sort((a, b) => String(a[1]).localeCompare(String(b[1]))); const frag = document.createDocumentFragment(); entries.forEach(([code, name]) => { const item = document.createElement("a"); item.className = "redesigned-dropdown-item nationality-item"; item.dataset.code = code; const img = document.createElement("img"); img.src = `https://flagsapi.com/${code}/flat/64.png`; img.alt = code; const text = document.createElement("span"); text.textContent = name; item.appendChild(img); item.appendChild(text); item.addEventListener("click", function () { const draft = getCurrentDraftElement(); if (!draft) return; draft.dataset.nationality = code; document.querySelector(".driver-info-driver-flag").src = `https://flagsapi.com/${code}/flat/64.png`; document.querySelector(".flag-text").textContent = inverted_countries_abreviations[code] || code; document.dispatchEvent(new CustomEvent("draft-nationality-selected", { detail: { draftId: draft.dataset.driverid, code } })); }); frag.appendChild(item); }); nationalityMenu.appendChild(frag); } export function setStatPanelShown(value) { statPanelShown = value; } export function setTypeOverall(value) { typeOverall = value; } export function setTypeEdit(value) { typeEdit = value; } /** * Removes all the staff from their list */ export function removeStatsDrivers(staffOnly = false) { document.querySelectorAll(".staff-list").forEach(function (elem) { if (elem.id === "fulldriverlist" && staffOnly === false) { elem.innerHTML = "" } else if (elem.id !== "fulldriverlist") { elem.innerHTML = "" } }) } /** * Places the drivers that the backend fetched on the driver list * @param {Object} driversArray Object with all the drivers that the backend fetched */ export function place_drivers_editStats(driversArray) { let divPosition; driversArray.forEach((driver) => { divPosition = "fulldriverlist" let newDiv = document.createElement("div"); let ovrDiv = document.createElement("div"); let ovrSpan = document.createElement("span"); newDiv.className = "col normal-driver"; newDiv.dataset.driverid = driver[1]; let nameDiv = document.createElement("div"); nameDiv.className = "name-div-edit-stats" newDiv.dataset.teamid = driver[2]; newDiv.dataset.type = 0; let name = driver[0].split(" ") let spanName = document.createElement("span") let spanLastName = document.createElement("span") format_name(driver[0], name, spanName, spanLastName) newDiv.dataset.name = insert_space(name[0]) + " " + name.slice(1).join(" ") spanLastName.classList.add("bold-font") spanLastName.classList.add("surname") nameDiv.appendChild(spanName) nameDiv.appendChild(spanLastName) manageColor(newDiv, spanLastName) newDiv.appendChild(nameDiv) newDiv.classList.add(team_dict[driver[2]] + "-transparent") let statsString = ''; for (let i = 5; i <= 15; i++) { statsString += driver[i] + ' '; } newDiv.dataset.stats = statsString; newDiv.dataset.superLicense = driver["superlicense"] newDiv.dataset.age = driver["age"] newDiv.dataset.retirement = driver["retirement_age"] newDiv.dataset.numWC = driver["wants1"] newDiv.dataset.number = driver["driver_number"] newDiv.dataset.raceFormula = driver["race_formula"] newDiv.dataset.driverCode = driver["driver_code"] newDiv.dataset.isRetired = driver[4] if (driver["nationality"] !== "") { newDiv.dataset.nationality = driver["nationality"] } if (driver["mentality0"] >= 0) { newDiv.dataset.mentality0 = driver["mentality0"] newDiv.dataset.mentality1 = driver["mentality1"] newDiv.dataset.mentality2 = driver["mentality2"] newDiv.dataset.globalMentality = driver["global_mentality"] } let mentality = driver["global_mentality"] newDiv.dataset.marketability = driver["marketability"] let ovr = calculateOverall(statsString, "driver") ovrSpan.textContent = ovr ovrDiv.appendChild(ovrSpan) ovrDiv.classList.add("bold-font") ovrDiv.classList.add("small-ovr") newDiv.appendChild(ovrDiv) newDiv.addEventListener('click', () => { if (!isComparisonModeActive) { let elementosClicked = document.querySelectorAll('.clicked'); elementosClicked.forEach(item => item.classList.remove('clicked')); newDiv.classList.toggle('clicked'); driverStatTitle.innerText = newDiv.dataset.name; load_stats(newDiv); if (statPanelShown == 0) { document.getElementById("editStatsPanel").className = "left-panel-stats"; statPanelShown = 1; } recalculateOverall(); } else if (isComparisonModeActive && firstDriverStats) { //remove clicked class from actual comparing driver let comparingDriver = document.querySelector('.comparing-driver'); if (comparingDriver) { comparingDriver.classList.remove('clicked', 'comparing-driver'); let nameDivOld = comparingDriver.children[0]; let comparingTagOld = nameDivOld.querySelector(".comparing-tag"); if (comparingTagOld) { nameDivOld.removeChild(comparingTagOld); } } //add clicked class newDiv.classList.add('clicked', 'comparing-driver'); let nameDiv = newDiv.children[0]; let comparingTag = document.createElement("span"); let teamClass = team_dict[newDiv.dataset.teamid]; comparingTag.className = `comparing-tag ${teamClass}`; comparingTag.textContent = "Comparing"; nameDiv.appendChild(comparingTag); secondDriverStats = newDiv.dataset.stats; updateComparisonUI(); } }); document.getElementById(divPosition).appendChild(newDiv) }) document.querySelector("#edit_stats").querySelectorAll(".custom-input-number").forEach(function (elem) { elem.addEventListener("change", function () { if (elem.value > 100) { elem.value = 100; } recalculateOverall() }); }); manage_order(0) } export function initStatsDrivers() { editStatsItems = [...document.querySelectorAll('.normal-driver:not([data-is-draft="1"])')].map(el => { const first = el.children[0]?.children[0]?.textContent || ""; const last = el.children[0]?.children[1]?.textContent || ""; return { el, name: (first + last).toLowerCase() }; }); } /** * Places the staff that the backend fetched on their respective staff list * @param {Object} staffArray Object with all the staff that the backend fetched */ export function place_staff_editStats(staffArray) { let divPosition; staffArray.forEach((staff) => { let statsString = ''; if (staff[3] == 1) { divPosition = "fullTechnicalList" for (let i = 4; i <= 9; i++) { statsString += staff[i] + ' '; } } else if (staff[3] == 2) { divPosition = "fullEngineerList" for (let i = 4; i <= 6; i++) { statsString += staff[i] + ' '; } } else if (staff[3] == 3) { divPosition = "fullAeroList" for (let i = 4; i <= 11; i++) { statsString += staff[i] + ' '; } } else if (staff[3] == 4) { divPosition = "fullDirectorList" for (let i = 4; i <= 7; i++) { statsString += staff[i] + ' '; } } statsString = statsString.slice(0, -1); let newDiv = document.createElement("div"); let ovrDiv = document.createElement("div"); let ovrSpan = document.createElement("span") newDiv.className = "col normal-driver"; newDiv.dataset.driverid = staff[1]; newDiv.dataset.type = staff[3]; let nameDiv = document.createElement("div"); nameDiv.className = "name-div-edit-stats" newDiv.dataset.teamid = staff[2]; let name = staff[0].split(" ") let spanName = document.createElement("span") let spanLastName = document.createElement("span") format_name(staff[0], name, spanName, spanLastName) newDiv.dataset.name = insert_space(name[0]) + " " + name.slice(1).join(" ") spanLastName.classList.add("bold-font") spanLastName.classList.add("surname") nameDiv.appendChild(spanName) nameDiv.appendChild(spanLastName) manageColor(newDiv, spanLastName) newDiv.appendChild(nameDiv) newDiv.classList.add(team_dict[staff[2]] + "-transparent") newDiv.dataset.stats = statsString; newDiv.dataset.age = staff["age"] newDiv.dataset.retirement = staff["retirement_age"] newDiv.dataset.raceFormula = staff["race_formula"] newDiv.dataset.isRetired = staff["is_retired"] ?? 0 if (staff["nationality"] !== "") { newDiv.dataset.nationality = staff["nationality"] } if (staff["mentality0"] >= 0) { newDiv.dataset.mentality0 = staff["mentality0"] newDiv.dataset.mentality1 = staff["mentality1"] newDiv.dataset.mentality2 = staff["mentality2"] newDiv.dataset.globalMentality = staff["global_mentality"] } let mentality = staff["global_mentality"] let ovr = calculateOverall(statsString, "staff") ovrSpan.textContent = ovr ovrDiv.appendChild(ovrSpan) ovrDiv.classList.add("bold-font") ovrDiv.classList.add("small-ovr") newDiv.appendChild(ovrDiv) newDiv.addEventListener('click', () => { if (!isComparisonModeActive) { let elementosClicked = document.querySelectorAll('.clicked'); elementosClicked.forEach(item => item.classList.remove('clicked')); newDiv.classList.toggle('clicked'); driverStatTitle.innerText = newDiv.dataset.name; load_stats(newDiv); if (statPanelShown == 0) { document.getElementById("editStatsPanel").className = "left-panel-stats"; statPanelShown = 1; } recalculateOverall(); } else if (isComparisonModeActive && firstDriverStats) { //remove clicked class from actual comparing driver let comparingDriver = document.querySelector('.comparing-driver'); if (comparingDriver) { comparingDriver.classList.remove('clicked', 'comparing-driver'); let nameDivOld = comparingDriver.children[0]; let comparingTagOld = nameDivOld.querySelector(".comparing-tag"); if (comparingTagOld) { nameDivOld.removeChild(comparingTagOld); } } //add clicked class newDiv.classList.add('clicked', 'comparing-driver'); let nameDiv = newDiv.children[0]; let comparingTag = document.createElement("span"); let teamClass = team_dict[newDiv.dataset.teamid]; comparingTag.className = `comparing-tag ${teamClass}`; comparingTag.textContent = "Comparing"; nameDiv.appendChild(comparingTag); secondDriverStats = newDiv.dataset.stats; updateComparisonUI(); } }); document.getElementById(divPosition).appendChild(newDiv) }) } function getMentalityModifier(mentality) { let keys = Object.keys(mentalityModifiers).map(Number).sort((a, b) => a - b); let nextKey = keys.find(key => key > mentality); return nextKey !== undefined ? mentalityModifiers[nextKey] : null; } /** * changes the overall placed in the overall square */ function recalculateOverall() { let stats = "" document.querySelectorAll(".elegible").forEach(function (elem) { stats += elem.value + " " }) stats = stats.slice(0, -1); let oldovr = document.getElementById("ovrholder").innerHTML; let ovr = calculateOverall(stats, typeOverall); if (oldovr > ovr) { document.getElementById("ovrholder").innerHTML = ovr; document.getElementById("ovrholder").className = "overall-holder bold-font alertNeg"; setTimeout(() => { document.getElementById("ovrholder").className = "overall-holder bold-font" }, 300); } else if (oldovr < ovr) { document.getElementById("ovrholder").innerHTML = ovr; document.getElementById("ovrholder").className = "overall-holder bold-font alertPos"; setTimeout(() => { document.getElementById("ovrholder").className = "overall-holder bold-font" }, 300); } } /** * eventListeenr for the confirm button for the stats */ /** * Gets the named with a space between name and lastname * @param {*} html element with the name bad formatted * @returns the name formatted */ export function getName(html) { let name = "" html.querySelectorAll('span').forEach(function (elem) { name += elem.innerText + " " }) name = name.slice(0, -1) return name; } /** * Mathematic calculations to get a staff's overall value * @param {string} stats all stats spearated by a space between them * @param {string} type type of staff * @returns the number of his overall value */ export function calculateOverall(stats, type) { let statsArray = stats.split(" ").map(Number); let rating; if (type === "driver") { let cornering = statsArray[0]; let braking = statsArray[1]; let control = statsArray[2]; let smoothness = statsArray[3]; let adaptability = statsArray[4]; let overtaking = statsArray[5]; let defence = statsArray[6]; let reactions = statsArray[7]; let accuracy = statsArray[8]; rating = (cornering + braking * 0.75 + reactions * 0.5 + control * 0.75 + smoothness * 0.5 + accuracy * 0.75 + adaptability * 0.25 + overtaking * 0.25 + defence * 0.25) / 5; } else if (type === "staff") { let suma = 0; for (let i = 0; i < statsArray.length; i++) { suma += statsArray[i]; } rating = suma / statsArray.length; } return Math.round(rating) } function updateStat(input, increment) { let val = parseInt(input.value) + increment; if (val > 100) val = 100; if (val < 0) val = 0; input.value = val; recalculateOverall(); manage_stat_bar(input, val); } document.querySelectorAll(".attributes-panel .bi-plus").forEach(button => { let bar = button.parentNode.parentNode.parentNode.querySelector(".one-stat-progress"); let statInput = button.parentNode.parentNode.querySelector("input"); attachHold(button, statInput, +1, { min: 0, max: 99, progressEl: bar, onChange: recalculateOverall }); recalculateOverall(); }); document.querySelectorAll(".attributes-panel .bi-dash").forEach(button => { let bar = button.parentNode.parentNode.parentNode.querySelector(".one-stat-progress"); let statInput = button.parentNode.parentNode.querySelector("input"); attachHold(button, statInput, -1, { min: 0, max: 99, progressEl: bar, onChange: recalculateOverall }); }); document.querySelectorAll("#addStaffTypeMenu a").forEach(item => { item.addEventListener("click", function () { setAddStaffType(item.dataset.staffType); const currentDraft = getCurrentDraftElement(); if (currentDraft && currentDraft.dataset.type !== item.dataset.staffType) { document.dispatchEvent(new CustomEvent("random-staff-requested", { detail: { typeStaff: item.dataset.staffType } })); } }); }); if (addStaffButton) { addStaffButton.addEventListener("click", function () { if (isComparisonModeActive) toggleComparisonMode(); document.dispatchEvent(new CustomEvent("random-staff-requested", { detail: { typeStaff: selectedAddStaffType } })); }); } if (genderSwapButton) { genderSwapButton.addEventListener("click", function () { const draft = getCurrentDraftElement(); if (!draft) return; const nextGender = draft.dataset.gender === "1" ? "0" : "1"; draft.dataset.gender = nextGender; updateDraftControlVisibility(draft); document.dispatchEvent(new CustomEvent("random-forename-requested", { detail: { draftId: draft.dataset.driverid, gender: nextGender, staffNameLocale: draft.dataset.staffNameLocale } })); }); } setAddStaffType(selectedAddStaffType); initNationalityDropdown(); attachHold(plusBtn, ageSpan, +1, { min: 0, max: 100 }); attachHold(minusBtn, ageSpan, -1, { min: 0, max: 100 }); attachHold(plusR, inputR, +1, { min: 30, max: 80 }); attachHold(minusR, inputR, -1, { min: 30, max: 80 }); document.querySelector("#nameFilter").addEventListener("input", function (event) { const val = event.target.value; clearIcon2.classList.toggle("d-none", val === ""); clearTimeout(timer); timer = setTimeout(() => { const q = val.trim().toLowerCase(); console.log("Filtering with query:", q); if (!q) { for (const { el } of editStatsItems) el.classList.remove("d-none"); return; } for (const { el, name } of editStatsItems) el.classList.toggle("d-none", !name.includes(q)); }, 150); }) document.querySelectorAll(".text-filter-container .bi-x").forEach(function (elem) { elem.addEventListener("click", function () { let input = elem.parentNode.querySelector("input") input.value = "" elem.classList.add("d-none") let event = new Event('input', { bubbles: true, cancelable: true }); input.dispatchEvent(event); }) }) document.querySelector("#filterIcon").addEventListener("click", function () { document.getElementById("edit_stats").querySelector(".category-filters").classList.toggle("show") document.getElementById("edit_stats").querySelector(".filter-container").classList.toggle("focused") if (document.getElementById("edit_stats").querySelector(".filter-container").classList.contains("focused")) { document.querySelector("#filterIcon").className = "bi bi-filter-circle-fill filter-icon" } else { document.querySelector("#filterIcon").className = "bi bi-filter-circle filter-icon" } }) document.getElementById("edit_stats").querySelectorAll(".new-pills-filters").forEach(function (elem) { elem.addEventListener("click", function (event) { let isActive = elem.classList.contains('active'); document.getElementById("edit_stats").querySelectorAll('.new-pills-filters').forEach(function (el) { el.classList.remove('active'); }); if (!isActive) { elem.classList.add('active'); } }) }) document.querySelector("#F1filter").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { if (parseInt(elem.dataset.raceFormula) === 1) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector("#F2filter").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { if (parseInt(elem.dataset.raceFormula) === 2) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector("#F3filter").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { if (parseInt(elem.dataset.raceFormula) === 3) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector("#freefilter").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])') elements.forEach(function (elem) { if (parseInt(elem.dataset.raceFormula) === 4) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector(".order-space").querySelectorAll("i").forEach(function (elem) { elem.addEventListener("click", function (event) { let parent = elem.parentNode let state = parent.dataset.state let orderNumUp = document.querySelector(".bi-sort-numeric-up-alt") let orderNumDown = document.querySelector(".bi-sort-numeric-down") parent.dataset.state = (parseInt(state) + 1) % 3 if (parent.dataset.state == 0) { orderNumUp.classList.remove("active") orderNumUp.classList.remove("hidden") orderNumDown.classList.add("hidden") } else if (parent.dataset.state == 1) { orderNumDown.classList.add("hidden") orderNumDown.classList.add("active") orderNumUp.classList.add("active") orderNumUp.classList.remove("hidden") } else if (parent.dataset.state == 2) { orderNumUp.classList.remove("active") orderNumUp.classList.add("hidden") orderNumDown.classList.add("active") orderNumDown.classList.remove("hidden") } manage_order(parseInt(parent.dataset.state)) }) }) /** * Adds eventListeners to all the elements of the staff dropdown */ export function listenersStaffGroups() { document.querySelectorAll('#staffMenu a').forEach(item => { item.addEventListener("click", function () { if (isComparisonModeActive) toggleComparisonMode(); const staffButton = document.getElementById('staffDropdown'); let staffSelected = item.innerHTML let staffCode = item.dataset.spacestats if (staffCode === "driverStats") { typeOverall = "driver" typeEdit = "0" document.getElementById("driverSpecialAttributes").classList.remove("d-none") document.querySelector("#superLicenseSwitch").classList.remove("d-none") document.querySelector("#driverCode").classList.remove("d-none") document.querySelector("#numberDetails").previousElementSibling.classList.remove("d-none") document.querySelector("#numberDetails").classList.remove("d-none") document.querySelector(".upper-section-stats").classList.add("showing-driver") } else { typeOverall = "staff" document.getElementById("driverSpecialAttributes").classList.add("d-none") document.querySelector("#superLicenseSwitch").classList.add("d-none") document.querySelector("#driverCode").classList.add("d-none") if (staffCode === "chiefStats") { typeEdit = "1" } if (staffCode === "engineerStats") { typeEdit = "2" } if (staffCode === "aeroStats") { typeEdit = "3" } if (staffCode === "directorStats") { typeEdit = "4" } document.querySelector("#numberDetails").previousElementSibling.classList.add("d-none") document.querySelector("#numberDetails").classList.add("d-none") document.querySelector(".upper-section-stats").classList.remove("showing-driver") } staffButton.querySelector(".dropdown-label").innerHTML = staffSelected; setAddStaffType(typeEdit); setAttributesTitle(typeEdit); change_elegibles(item.dataset.spacestats) document.querySelectorAll(".staff-list").forEach(function (elem) { elem.classList.add("d-none") if (item.dataset.list == elem.id) { elem.classList.remove("d-none") } }) document.querySelector(".left-panel-stats").classList.add("d-none") statPanelShown = 0; }); }); document.getElementById("driverStatsDrop").click() } function setAddStaffType(typeStaff) { if (!addStaffTypeDropdown) return; const nextType = String(typeStaff ?? "0"); const config = staffTypeConfig[nextType] || staffTypeConfig["0"]; selectedAddStaffType = nextType; addStaffTypeDropdown.querySelector(".dropdown-label").textContent = config.label; } function resetStatsFilters() { const nameFilter = document.querySelector("#nameFilter"); if (nameFilter && nameFilter.value !== "") { nameFilter.value = ""; nameFilter.dispatchEvent(new Event("input", { bubbles: true, cancelable: true })); } document.getElementById("edit_stats").querySelectorAll(".new-pills-filters").forEach(function (elem) { elem.classList.remove("active"); }); document.querySelectorAll('.normal-driver:not([data-is-draft="1"])').forEach(function (elem) { elem.classList.remove("d-none"); }); } function applyStaffTypeSelection(typeStaff) { // Deprecated: draft type switching should not affect the browse dropdown. } function getDraftContainer() { return document.querySelector(".staff-list:not(.d-none)") || document.getElementById("fulldriverlist") || document.getElementById("placeholder"); } function getCurrentDraftElement() { return document.querySelector('.normal-driver[data-is-draft="1"]'); } function removeCurrentDraftElement() { const draft = getCurrentDraftElement(); if (draft) { draft.remove(); } currentDraftId = null; } function createDraftElement(profile) { const newDiv = document.createElement("div"); const ovrDiv = document.createElement("div"); const ovrSpan = document.createElement("span"); const nameDiv = document.createElement("div"); const spanName = document.createElement("span"); const spanLastName = document.createElement("span"); const nameParts = profile.name.split(" "); newDiv.className = "col normal-driver draft-profile d-none"; newDiv.dataset.isDraft = "1"; newDiv.dataset.driverid = profile.draftId; newDiv.dataset.teamid = profile.teamid ?? 0; newDiv.dataset.type = profile.typeStaff; newDiv.dataset.name = profile.name; newDiv.dataset.gender = String(profile.gender ?? 0); newDiv.dataset.staffNameLocale = profile.staffNameLocale ?? ""; newDiv.dataset.firstNameLocKey = profile.firstNameLocKey ?? ""; newDiv.dataset.lastNameLocKey = profile.lastNameLocKey ?? ""; newDiv.dataset.stats = profile.stats; newDiv.dataset.age = profile.age; newDiv.dataset.retirement = profile.retirement_age; newDiv.dataset.number = profile.driver_number ?? 0; newDiv.dataset.numWC = profile.wants1 ?? 0; newDiv.dataset.superLicense = profile.superlicense ?? 0; newDiv.dataset.driverCode = profile.driver_code ?? ""; newDiv.dataset.isRetired = profile.isRetired ?? 0; newDiv.dataset.raceFormula = profile.race_formula ?? 4; newDiv.dataset.marketability = profile.marketability ?? ""; newDiv.dataset.nationality = profile.nationality ?? ""; newDiv.dataset.mentality0 = profile.mentality0 ?? ""; newDiv.dataset.mentality1 = profile.mentality1 ?? ""; newDiv.dataset.mentality2 = profile.mentality2 ?? ""; newDiv.dataset.globalMentality = profile.global_mentality ?? ""; nameDiv.className = "name-div-edit-stats"; format_name(profile.name, nameParts, spanName, spanLastName); spanLastName.classList.add("bold-font", "surname"); nameDiv.appendChild(spanName); nameDiv.appendChild(spanLastName); manageColor(newDiv, spanLastName); newDiv.appendChild(nameDiv); ovrSpan.textContent = calculateOverall(profile.stats, profile.typeStaff === "0" ? "driver" : "staff"); ovrDiv.appendChild(ovrSpan); ovrDiv.classList.add("bold-font", "small-ovr"); newDiv.appendChild(ovrDiv); return newDiv; } function updateDraftControlVisibility(div) { const isDraft = div?.dataset?.isDraft === "1"; draftStaffTypeControl?.classList.toggle("d-none", !isDraft); genderSwapButton?.classList.toggle("d-none", !isDraft); if (nationalityButton) nationalityButton.disabled = !isDraft; if (!isDraft || !genderSwapButton) return; const isFemale = div.dataset.gender === "1"; const icon = genderSwapButton.querySelector("i"); const label = genderSwapButton.querySelector(".button-text"); icon.className = isFemale ? "bi bi-gender-female" : "bi bi-gender-male"; label.textContent = isFemale ? "Female" : "Male"; } function enterNameEditMode() { const button = document.querySelector("#editNameButton"); if (button && !button.classList.contains("editing")) { button.click(); } } export function isDraftProfileSelected() { return document.querySelector(".clicked")?.dataset?.isDraft === "1"; } function setEditorStaffType(typeStaff) { const typeStr = String(typeStaff ?? "0"); const config = staffTypeConfig[typeStr] || staffTypeConfig["0"]; const isDriver = typeStr === "0"; typeEdit = typeStr; typeOverall = isDriver ? "driver" : "staff"; setAttributesTitle(typeStr); if (isDriver) { document.getElementById("driverSpecialAttributes").classList.remove("d-none"); document.querySelector("#superLicenseSwitch").classList.remove("d-none"); document.querySelector("#driverCode").classList.remove("d-none"); document.querySelector("#numberDetails").previousElementSibling.classList.remove("d-none"); document.querySelector("#numberDetails").classList.remove("d-none"); document.querySelector(".upper-section-stats").classList.add("showing-driver"); } else { document.getElementById("driverSpecialAttributes").classList.add("d-none"); document.querySelector("#superLicenseSwitch").classList.add("d-none"); document.querySelector("#driverCode").classList.add("d-none"); document.querySelector("#numberDetails").previousElementSibling.classList.add("d-none"); document.querySelector("#numberDetails").classList.add("d-none"); document.querySelector(".upper-section-stats").classList.remove("showing-driver"); } change_elegibles(config.spaceStats); } export function loadRandomStaffDraft(profile) { if (isComparisonModeActive) toggleComparisonMode(); resetStatsFilters(); removeCurrentDraftElement(); const editNameButton = document.querySelector("#editNameButton"); if (editNameButton?.classList.contains("editing")) { editNameButton.click(); } setAddStaffType(profile.typeStaff); setEditorStaffType(profile.typeStaff); document.querySelectorAll('.clicked').forEach(item => item.classList.remove('clicked')); const draftDiv = createDraftElement(profile); draftDiv.classList.add("clicked"); getDraftContainer()?.appendChild(draftDiv); currentDraftId = profile.draftId; driverStatTitle.innerText = draftDiv.dataset.name; load_stats(draftDiv); if (statPanelShown == 0) { document.getElementById("editStatsPanel").className = "left-panel-stats"; statPanelShown = 1; } recalculateOverall(); enterNameEditMode(); } export function applyDraftForenameUpdate(payload) { const draft = getCurrentDraftElement(); if (!draft) return; if (String(draft.dataset.driverid) !== String(payload.draftId)) return; const firstName = payload.firstName || ""; draft.dataset.firstNameLocKey = payload.firstNameLocKey || ""; if (payload.lastNameLocKey) draft.dataset.lastNameLocKey = payload.lastNameLocKey || ""; const currentLastName = (draft.dataset.name || "").split(" ").slice(1).join(" ").trim(); const lastName = payload.lastName || currentLastName; const newName = `${firstName} ${lastName}`.trim(); draft.dataset.name = newName; const nameDiv = draft.querySelector(".name-div-edit-stats"); if (nameDiv) { nameDiv.innerHTML = ""; const spanName = document.createElement("span"); const spanLastName = document.createElement("span"); const parts = newName.split(" "); format_name(newName, parts, spanName, spanLastName); spanLastName.classList.add("bold-font", "surname"); nameDiv.appendChild(spanName); nameDiv.appendChild(spanLastName); manageColor(draft, spanLastName); } const nameTextarea = document.querySelector("#driverStatsTitle textarea"); if (nameTextarea) { nameTextarea.value = newName; } else { driverStatTitle.textContent = newName; } } export function applyDraftCountryLocale(payload) { const draft = getCurrentDraftElement(); if (!draft) return; if (String(draft.dataset.driverid) !== String(payload.draftId)) return; draft.dataset.staffNameLocale = payload.staffNameLocale ?? ""; draft.dataset.countryId = payload.countryId ?? ""; draft.dataset.countryName = payload.countryName ?? ""; } function manage_order(state) { let elements = document.querySelectorAll('.normal-driver:not([data-is-draft="1"])'); let array = Array.from(elements); // Crear un objeto para almacenar los padres originales let parents = {}; array.forEach(elem => { parents[elem.dataset.driverid] = elem.parentNode; // Asumiendo que cada .normal-driver tiene un data-id único }); let sortedArray = array.sort(function (a, b) { let ovrA = parseInt(a.children[1].innerText); let ovrB = parseInt(b.children[1].innerText); let teamA = parseInt(a.dataset.teamid); let teamB = parseInt(b.dataset.teamid); if (state == 0) { if (teamA === 0) return 1; if (teamB === 0) return -1; let indexA = teamOrder.indexOf(teamA); let indexB = teamOrder.indexOf(teamB); if (indexA !== indexB) { return indexA - indexB; } return ovrB - ovrA; } else if (state == 1) { return ovrB - ovrA; } else { return ovrA - ovrB; } }); // Limpiar los contenedores document.querySelectorAll(".staff-list").forEach(function (elem) { if (elem.id === "placeholder") return; elem.innerHTML = ""; }); // Volver a colocar los elementos ordenados en sus padres originales sortedArray.forEach(function (elem) { let parent = parents[elem.dataset.driverid]; parent.appendChild(elem); }); } export function manage_stat_bar(element, value) { let container = element.parentNode.parentNode.parentNode let bar = container.querySelector(".one-stat-progress") let percentage = value + "%" bar.style.width = percentage } /** * Loads the stats into the input numbers * @param {div} div div of the staff that is about to be edited */ function load_stats(div) { const editNameButton = document.querySelector("#editNameButton"); if (editNameButton?.classList.contains("editing") && div.dataset.isDraft !== "1") { editNameButton.click(); } let statsArray = div.dataset.stats.split(" ").map(Number); let inputArray = document.querySelectorAll(".elegible") inputArray.forEach(function (input, index) { let value = statsArray[index] let label = input.parentNode.parentNode.querySelector("span.bold-font") input.value = value manage_stat_bar(input, value) }); const graphInputArray = document.querySelectorAll(".elegible"); const pairs = Array.from(graphInputArray).map((input, index) => { const labelEl = input.parentNode?.parentNode?.querySelector("span.bold-font"); const labelFull = (labelEl?.textContent || '').trim(); const value = statsArray[index]; return { labelFull, value }; }); // Excluir Growth y Aggression (incluida variante "Aggresion") const excluded = new Set(['growth', 'aggression', 'aggresion', 'marketability']); const filtered = pairs.filter(p => !excluded.has(p.labelFull.toLowerCase())); // Labels = 3 primeras letras en MAYÚSCULAS const labelsArray = filtered.map(p => p.labelFull.slice(0, 3).toUpperCase()); const valuesArray = filtered.map(p => p.value); // (Re)crear si cambian etiquetas; si no, solo actualizar datos if (!statsRadarChart || statsRadarChart.data.labels.length !== labelsArray.length || statsRadarChart.data.labels.some((l, i) => l !== labelsArray[i])) { createStatsRadarChart(labelsArray); statsRadarChart.config._fullLabels = filtered.map(p => p.labelFull); } updateStatsRadarData(valuesArray, 0, cssVar("--new-primary"), div.dataset.name.split(" ").pop()); let actualAge = document.querySelector(".actual-age") let retirementAge = document.querySelector(".actual-retirement") let numberHolder = document.querySelector(".number-holder") let numberWC = document.querySelector("#driverNumber1") let codeInput = document.querySelector("#driverCode") codeInput.innerText = div.dataset.driverCode oldNum = div.dataset.number actualAge.innerText = div.dataset.age retirementAge.innerText = div.dataset.retirement numberHolder.innerText = div.dataset.number if (div.dataset.numWC === "0") { numberWC.checked = false } else { numberWC.checked = true } if (div.dataset.superLicense === "1") { document.querySelector("#superLicense").checked = true } else { document.querySelector("#superLicense").checked = false } if (div.dataset.isRetired === "1") { document.querySelector("#retiredInput").checked = true } else { document.querySelector("#retiredInput").checked = false } if (div.dataset.mentality0) { for (let i = 0; i < 3; i++) { let mentality = div.dataset["mentality" + i] let indicator = document.getElementById("mentality" + i) indicator.parentNode.parentNode.classList.remove("d-none") indicator.dataset.value = mentality let inverted_value = 5 - mentality let levels = indicator.querySelectorAll('.mentality-level'); let mentality_class = mentality_dict[mentality] for (let j = 0; j < 5; j++) { levels[j].className = "mentality-level" if (j <= inverted_value - 1) { levels[j].classList.add(mentality_class) } } let nameEmoji = indicator.parentNode.parentNode.querySelector(".mentality-and-emoji") nameEmoji.innerText = capitalizeFirstLetter(mentality_class) nameEmoji.className = "mentality-and-emoji" nameEmoji.classList.add(mentality_class) } } else { for (let i = 0; i < 3; i++) { let indicator = document.getElementById("mentality" + i) indicator.parentNode.parentNode.classList.add("d-none") } } if (div.dataset.marketability) { document.querySelector("#marketability").classList.remove("d-none") document.getElementById("marketabilityInput").value = div.dataset.marketability document.getElementById("marketabilityBar").style.width = div.dataset.marketability + "%" } else { document.querySelector("#marketability").classList.add("d-none") } if (div.dataset.nationality) { document.querySelector(".driver-info-driver-flag").src = `https://flagsapi.com/${div.dataset.nationality}/flat/64.png` document.querySelector(".flag-text").textContent = inverted_countries_abreviations[div.dataset.nationality] || div.dataset.nationality } const teamId = Number(div.dataset.teamid); const logoImg = document.querySelector(".driver-info-team-logo-img"); const logoMasked = document.querySelector(".driver-info-team-logo-masked"); const logo = logos_disc[teamId]; if (!logoImg || !logoMasked || !teamId || !logo) { logoImg?.classList.add("d-none"); logoMasked?.classList.add("d-none"); } else if (teamId === 2) { logoImg.classList.add("d-none"); logoMasked.classList.remove("d-none"); } else { logoMasked.classList.add("d-none"); logoImg.classList.remove("d-none"); logoImg.src = logo; } let teamName = combined_dict[div.dataset.teamid] || "Free Agent"; document.querySelector(".team-text").textContent = teamName !== "Visa Cashapp RB" ? teamName : "VCARB"; updateDraftControlVisibility(div); setAttributesTitle(div.dataset.type); } document.querySelectorAll(".bar-container .bi-chevron-right").forEach(function (elem) { elem.addEventListener("click", function () { let indicator = elem.parentNode.querySelector(".mentality-level-indicator") let value = parseInt(indicator.getAttribute('data-value')) - 1; if (value < 0) { value = 0 } let inverted_value = 5 - value indicator.setAttribute('data-value', value); let levels = indicator.querySelectorAll('.mentality-level'); let mentality_class = mentality_dict[value] for (let j = 0; j < 5; j++) { levels[j].className = "mentality-level" if (j <= inverted_value - 1) { levels[j].classList.add(mentality_class) } } let nameEmoji = elem.parentNode.parentNode.querySelector(".mentality-and-emoji") nameEmoji.innerText = capitalizeFirstLetter(mentality_class) nameEmoji.className = "mentality-and-emoji" nameEmoji.classList.add(mentality_class) }) }) document.querySelectorAll(".bar-container .bi-chevron-left").forEach(function (elem) { elem.addEventListener("click", function () { let indicator = elem.parentNode.querySelector(".mentality-level-indicator") let value = parseInt(indicator.getAttribute('data-value')) + 1; if (value > 4) { value = 4 } let inverted_value = 5 - value indicator.setAttribute('data-value', value); let levels = indicator.querySelectorAll('.mentality-level'); let mentality_class = mentality_dict[value] for (let j = 0; j < 5; j++) { levels[j].className = "mentality-level" if (j <= inverted_value - 1) { levels[j].classList.add(mentality_class) } } let nameEmoji = elem.parentNode.parentNode.querySelector(".mentality-and-emoji") nameEmoji.innerText = capitalizeFirstLetter(mentality_class) nameEmoji.className = "mentality-and-emoji" nameEmoji.classList.add(mentality_class) }) }) /** * Loads all the numbers into the number menu * @param {Object} nums all numbers array */ export function loadNumbers(nums) { numbersAvailable = nums; } attachHold(plusNumberBtn, numberSpan, +1, { min: 0, max: 99, }); attachHold(minusNumberBtn, numberSpan, -1, { min: 0, max: 99 }); document.querySelector("#editNameButton").addEventListener("click", function (e) { const btn = e.target; const nameSpan = document.getElementById("driverStatsTitle"); const codeSpan = document.getElementById("driverCode"); if (!btn.classList.contains("editing")) { // --- ENTRAMOS EN MODO EDICIÓN --- btn.className = "bi bi-check editing"; // Guardar tamaños originales como dataset const nameRect = nameSpan.getBoundingClientRect(); const codeRect = codeSpan.getBoundingClientRect(); nameSpan.dataset.originalWidth = nameRect.width; nameSpan.dataset.originalHeight = nameRect.height; codeSpan.dataset.originalWidth = codeRect.width; codeSpan.dataset.originalHeight = codeRect.height; // Crear textareas const nameInput = document.createElement("textarea"); const codeInput = document.createElement("textarea"); // Asignar valores nameInput.value = nameSpan.innerText.trim(); codeInput.value = codeSpan.innerText.trim(); const newNameWidth = nameRect.width; const newCodeWidth = codeRect.width; // Asignar tamaño nameInput.style.width = newNameWidth + "px"; nameInput.style.height = nameRect.height + "px"; nameSpan.style.width = newNameWidth + "px"; nameSpan.style.height = nameRect.height + "px"; codeInput.style.width = newCodeWidth + "px"; codeInput.style.height = codeRect.height + "px"; codeSpan.style.width = newCodeWidth + "px"; codeSpan.style.height = codeRect.height + "px"; // Reemplazar contenido nameSpan.innerHTML = ""; nameSpan.appendChild(nameInput); codeSpan.innerHTML = ""; codeSpan.appendChild(codeInput); } else { // --- GUARDAMOS CAMBIOS --- btn.className = "bi bi-pencil-fill"; btn.classList.remove("editing"); const nameInput = nameSpan.querySelector("textarea"); const codeInput = codeSpan.querySelector("textarea"); // Restaurar tamaño original nameSpan.style.width = "auto" nameSpan.style.height = "auto"; codeSpan.style.width = "auto"; codeSpan.style.height = "auto"; // Restaurar texto nameSpan.innerText = nameInput.value.trim(); codeSpan.innerText = codeInput.value.trim(); // Limpiar los datasets delete nameSpan.dataset.originalWidth; delete nameSpan.dataset.originalHeight; delete codeSpan.dataset.originalWidth; delete codeSpan.dataset.originalHeight; } }); function capitalizeFirstLetter(str) { if (!str) return str; // Manejo de cadena vacía return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); } /** * Generates the name title on the main panel of the edit stats * @param {div} html div from the staff selected * @returns the html necessary to put in the name with correct color */ function manage_stats_title(html) { let colorClass = "" if (html.dataset.teamid != 0) { colorClass = team_dict[html.dataset.teamid] + "font" } let spanName = document.createElement("span") let spanLastName = document.createElement("span") let name = "" + html.children[0].children[0].innerText + " " + "" + html.children[0].children[1].innerText + "" //let name = html.substring(0,html.length - 2).trim(); return name; } /** * Changes the input number that are taken into account to change stats * @param {div} divID div that contains the correct input numbers */ export function change_elegibles(divID) { document.querySelectorAll(".elegible").forEach(function (elem) { elem.classList.remove("elegible") }) let divStats = document.getElementById(divID) divStats.querySelectorAll(".custom-input-number").forEach(function (elem) { elem.classList.add("elegible") }) if (divID === "driverStats") { document.getElementById("growthInput").classList.add("elegible") document.getElementById("agressionInput").classList.add("elegible") } document.querySelectorAll(".main-panel-stats").forEach(function (elem) { elem.className = "main-panel-stats d-none" }) divStats.classList.remove("d-none") } function cssVar(name, fallback) { const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); return v || fallback; } function ensureStatsGraphCanvas() { const wrap = document.querySelector('.stats-graph'); let canvas = wrap.querySelector('canvas'); if (!canvas) { canvas = document.createElement('canvas'); canvas.id = 'statsRadar'; wrap.innerHTML = ''; // por si acaso wrap.appendChild(canvas); } return canvas.getContext('2d'); } function getThemeColor(fallback = '#4DA3FF') { // intenta leer de CSS variables; ajusta nombres si ya las tienes const root = getComputedStyle(document.documentElement); const c = root.getPropertyValue('--accent')?.trim() || root.getPropertyValue('--primary')?.trim() || fallback; return c; } function rgbaFromHex(hex, alpha) { // admite #RGB o #RRGGBB let h = hex.replace('#', ''); if (h.length === 3) h = h.split('').map(x => x + x).join(''); const r = parseInt(h.slice(0, 2), 16); const g = parseInt(h.slice(2, 4), 16); const b = parseInt(h.slice(4, 6), 16); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } function createStatsRadarChart(labels) { const ctx = ensureStatsGraphCanvas(); if (statsRadarChart) { statsRadarChart.destroy(); statsRadarChart = null; } const labelColor = cssVar('--text-general', '#e8eaed'); const primaryColor = cssVar('--new-primary', '#c89efc'); const secondaryColor = cssVar('--new-secondary', '#9efcc8'); statsRadarChart = new Chart(ctx, { type: 'radar', data: { labels, datasets: [{ label: 'Stats', data: [], borderColor: primaryColor, backgroundColor: `${primaryColor}40`, borderWidth: 2, pointRadius: 2, pointHoverRadius: 4, pointBackgroundColor: primaryColor }] }, options: { responsive: true, maintainAspectRatio: false, interaction: { mode: 'index', }, plugins: { legend: { display: false }, datalabels: { display: false }, tooltip: { enabled: true, backgroundColor: 'rgba(0, 0, 0, 0.8)', titleFont: { family: 'Formula1Bold', size: 13 }, bodyFont: { family: 'Formula1', size: 12 }, callbacks: { // Mostrar el nombre completo de la stat title: function (tooltipItems) { const index = tooltipItems[0].dataIndex; // Recupera el nombre completo desde tu array original // (debes tenerlo guardado globalmente o en chart.config._fullLabels) const fullLabel = statsRadarChart?.config?._fullLabels?.[index]; return fullLabel || tooltipItems[0].label; }, // Línea del dataset label: function (context) { const datasetLabel = context.dataset.label || ''; const value = context.formattedValue; return `${datasetLabel}: ${value}`; } } } }, scales: { r: { min: 0, max: 100, ticks: { display: false, showLabelBackdrop: false, stepSize: 50 }, grid: { color: 'rgba(128,128,128,0.25)' }, angleLines: { color: 'rgba(128,128,128,0.25)' }, pointLabels: { color: labelColor, font: { family: 'Formula1Bold' } } } }, layout: { padding: { top: 8, bottom: 16, left: -20, right: -20 } }, elements: { point: { radius: 2, hoverRadius: 4, hitRadius: 12 }, line: { tension: 0 } } } }); } function removeDatasetFromStatsRadarData(index) { if (!statsRadarChart) return; if (index < 0 || index >= statsRadarChart.data.datasets.length) return; statsRadarChart.data.datasets.splice(index, 1); recalculateRadarScale(); statsRadarChart.update(); } function updateStatsRadarData(values, index = 0, color, name) { if (!statsRadarChart) return; statsRadarChart.data.datasets[index].data = values; if (color) { statsRadarChart.data.datasets[index].borderColor = color; statsRadarChart.data.datasets[index].backgroundColor = rgbaFromHex(color, 0.25); } if (name) { statsRadarChart.data.datasets[index].label = name; } recalculateRadarScale(); statsRadarChart.update(); } function addDatasetToStatsRadarData(values, color, name) { if (!statsRadarChart) return; statsRadarChart.data.datasets.push({ label: name || `Stats ${statsRadarChart.data.datasets.length + 1}`, data: values, borderColor: color, backgroundColor: rgbaFromHex(color, 0.25), borderWidth: 2, pointRadius: 2, pointHoverRadius: 4, pointBackgroundColor: color }); recalculateRadarScale(); statsRadarChart.update(); } function recalculateRadarScale() { if (!statsRadarChart) return; // 1️⃣ Obtenemos todos los valores de todos los datasets const allValues = statsRadarChart.data.datasets.flatMap(ds => ds.data); // 2️⃣ Calculamos el mínimo y máximo reales const minVal = Math.min(...allValues); const maxVal = Math.max(...allValues); // 3️⃣ Creamos un margen dinámico (por ejemplo, 20 por debajo y 5 por encima) const lowerMargin = 15; const upperMargin = 5; // 4️⃣ Ajustamos la escala del radar chart statsRadarChart.options.scales.r.min = Math.max(0, minVal - lowerMargin); statsRadarChart.options.scales.r.max = Math.min(100, maxVal + upperMargin); } function toggleComparisonMode() { isComparisonModeActive = !isComparisonModeActive; const editStatsPanel = document.getElementById('editStatsPanel'); const header = document.querySelector('.upper-section-stats'); if (isComparisonModeActive) { compareButton.querySelector("span").textContent = 'Cancel'; compareButton.classList.add('active'); editStatsPanel.classList.add('comparison-active'); header.classList.add('comparison-active'); document.querySelectorAll(".mentality-and-emoji").forEach(indicator => { indicator.classList.add("d-none"); }); const clickedDriver = document.querySelector('.normal-driver.clicked'); if (clickedDriver) { firstDriverStats = clickedDriver.dataset.stats; let nameDiv = clickedDriver.children[0]; let comparingTag = document.createElement("span"); let teamClass = team_dict[clickedDriver.dataset.teamid]; comparingTag.className = `comparing-tag ${teamClass}`; comparingTag.textContent = "Comparing"; nameDiv.appendChild(comparingTag); } else { // Handle case where no driver is selected, maybe disable the button? console.warn("No driver selected for comparison."); // possibly exit comparison mode if no driver is selected to start with isComparisonModeActive = false; compareButton.querySelector("span").textContent = 'Compare'; editStatsPanel.classList.remove('comparison-active'); header.classList.remove('comparison-active'); } } else { //remove all comparison tags document.querySelectorAll('.normal-driver .comparing-tag').forEach(tag => tag.remove()); //remove comparing-driver class let comparingDriver = document.querySelector('.comparing-driver'); if (comparingDriver) { comparingDriver.classList.remove('comparing-driver', 'clicked'); } compareButton.classList.remove('active'); compareButton.querySelector("span").textContent = 'Compare'; editStatsPanel.classList.remove('comparison-active'); header.classList.remove('comparison-active'); firstDriverStats = null; secondDriverStats = null; resetComparisonUI(); removeDatasetFromStatsRadarData(1); document.querySelectorAll(".mentality-and-emoji").forEach(indicator => { indicator.classList.remove("d-none"); }); document.querySelectorAll(".shorten-ret").forEach(elem => { elem.innerText = "Retirement" }); // Remove cloned elements const clonedInfo = document.querySelector('.name-and-info.cloned'); if (clonedInfo) clonedInfo.remove(); const clonedOvr = document.querySelector('.special-overall.cloned'); if (clonedOvr) clonedOvr.remove(); document.querySelectorAll('.cloned-separator').forEach(separator => separator.remove()); const clonedAgeDetails = document.querySelector('#ageDetails.cloned'); if (clonedAgeDetails) clonedAgeDetails.remove(); document.querySelectorAll(".hidable-separator").forEach(separator => { separator.classList.remove("d-none"); }); } } if (compareButton) { compareButton.addEventListener('click', toggleComparisonMode); } function resetComparisonUI() { // Restore UI to single-driver view const statPanels = document.querySelectorAll('.one-stat-panel:has(.elegible)'); statPanels.forEach(panel => { const barContainer = panel.querySelector('.bar-container'); if (barContainer) barContainer.classList.remove('comparing'); //reset bar colors const actualBar = panel.querySelector('.one-stat-progress'); if (actualBar) actualBar.style.backgroundColor = ''; const input = panel.querySelector('input.custom-input-number'); if (input) { input.removeAttribute('readonly'); input.classList.remove('comparing-tag'); // Also remove any team color classes that might have been added for (const key in team_dict) { if (team_dict.hasOwnProperty(key)) { input.classList.remove(team_dict[key]); } } } const comparisonBar = panel.querySelector('.comparison-bar'); if (comparisonBar) comparisonBar.remove(); const comparisonValue = panel.querySelector('.comparison-stat-value'); if (comparisonValue) comparisonValue.remove(); const plusButton = panel.querySelector('.bi-plus'); const minusButton = panel.querySelector('.bi-dash'); if (plusButton) plusButton.style.display = ''; if (minusButton) minusButton.style.display = ''; const header = document.querySelector('.upper-section-stats'); // header.querySelector("#ageDetails").classList.remove("d-none"); header.querySelector("#numberDetails").classList.remove("d-none"); header.querySelector("#availabilityDetails").classList.remove("d-none"); }); // Reset Marketability const marketabilityPanel = document.getElementById('marketability'); if (marketabilityPanel) { const barContainer = marketabilityPanel.querySelector('.bar-container'); if (barContainer) barContainer.classList.remove('comparing'); const actualBar = marketabilityPanel.querySelector('.one-stat-progress'); if (actualBar) actualBar.style.backgroundColor = ''; const comparisonBar = marketabilityPanel.querySelector('.comparison-bar'); if (comparisonBar) comparisonBar.remove(); const comparisonValue = marketabilityPanel.querySelector('.comparison-stat-value'); if (comparisonValue) comparisonValue.remove(); const plusButton = marketabilityPanel.querySelector('.bi-plus'); const minusButton = marketabilityPanel.querySelector('.bi-dash'); if (plusButton) plusButton.style.display = ''; if (minusButton) minusButton.style.display = ''; const comparisonValueInput = marketabilityPanel.querySelector('.custom-input-number'); if (comparisonValueInput){ comparisonValueInput.removeAttribute('readonly'); comparisonValueInput.className = "custom-input-number elegible"; } } // Reset Mentality for (let i = 0; i < 3; i++) { const mentalityPanel = document.getElementById(`mentality${i}`).parentNode.parentNode; if (mentalityPanel) { const comparisonBar = mentalityPanel.querySelector('.comparison-bar'); if (comparisonBar) comparisonBar.remove(); const buttons = mentalityPanel.querySelectorAll('.bi-chevron-left, .bi-chevron-right'); buttons.forEach(btn => btn.style.display = ''); } } } function updateComparisonUI() { if (!firstDriverStats || !secondDriverStats) return; //get team ids from both drivers const teamId1 = document.querySelector('.normal-driver.clicked:not(.comparing-driver)').dataset.teamid; const teamId2 = document.querySelector('.normal-driver.clicked.comparing-driver').dataset.teamid; let secondColorSuffix = teamId1 === teamId2 ? '1' : '0'; let color2 = cssVar(`--new-secondary`) const stats1 = firstDriverStats.split(' ').map(Number); const stats2 = secondDriverStats.split(' ').map(Number); const statPanels = document.querySelectorAll('.one-stat-panel:has(.elegible)'); statPanels.forEach((panel, index) => { if (index < stats1.length) { // Remove previous comparison elements if they exist const existingComparisonBar = panel.querySelector('.comparison-stat-progress'); if (existingComparisonBar) existingComparisonBar.parentElement.remove(); const existingComparisonValue = panel.querySelector('.comparison-stat-value'); if (existingComparisonValue) existingComparisonValue.remove(); // Create and append the second stat bar const barContainer = panel.querySelector('.bar-container'); barContainer.classList.add('comparing'); let actualBar = barContainer.querySelector('.one-stat-progress'); const comparisonStatBarContainer = document.createElement('div'); comparisonStatBarContainer.className = 'one-stat-bar comparison-bar'; const comparisonProgressBar = document.createElement('div'); comparisonProgressBar.className = 'one-stat-progress comparison-stat-progress'; comparisonProgressBar.style.width = `${stats2[index]}%`; comparisonProgressBar.style.backgroundColor = color2; comparisonStatBarContainer.append(comparisonProgressBar); barContainer.appendChild(comparisonStatBarContainer); // Create and append the second stat value const statNumberDiv = panel.querySelector('.stat-number'); const comparisonValueInput = document.createElement('input'); //make the input non-editable comparisonValueInput.setAttribute('readonly', 'readonly'); comparisonValueInput.className = 'custom-input-number comparison-stat-value'; comparisonValueInput.value = stats2[index]; const existingValueInput = statNumberDiv.querySelector('input.custom-input-number:not(.comparison-stat-value)'); existingValueInput.setAttribute('readonly', 'readonly'); if (stats2[index] > stats1[index]) { comparisonValueInput.classList.add(`comparing-tag`, `secondary`); existingValueInput.classList.remove("comparing-tag", "primary"); } else if (stats2[index] < stats1[index]) { if (existingValueInput) existingValueInput.classList.add(`comparing-tag`, `primary`); comparisonValueInput.classList.remove("comparing-tag", "secondary"); } statNumberDiv.appendChild(comparisonValueInput); // Hide plus/minus buttons const plusButton = statNumberDiv.querySelector('.bi-plus'); const minusButton = statNumberDiv.querySelector('.bi-dash'); if (plusButton) plusButton.style.display = 'none'; if (minusButton) minusButton.style.display = 'none'; } }); // Update Marketability const marketabilityPanel = document.getElementById('marketability'); if (marketabilityPanel) { const driver1 = document.querySelector('.normal-driver.clicked:not(.comparing-driver)'); const driver2 = document.querySelector('.normal-driver.clicked.comparing-driver'); if (driver1.dataset.marketability && driver2.dataset.marketability) { const marketability1 = driver1.dataset.marketability; const marketability2 = driver2.dataset.marketability; //remove previous comparison elements if they exist const existingComparisonBar = marketabilityPanel.querySelector('.comparison-stat-progress'); if (existingComparisonBar) existingComparisonBar.parentElement.remove(); const existingComparisonValue = marketabilityPanel.querySelector('.comparison-stat-value'); if (existingComparisonValue) existingComparisonValue.remove(); const barContainer = marketabilityPanel.querySelector('.bar-container'); barContainer.classList.add('comparing'); let actualBar = barContainer.querySelector('.one-stat-progress'); //remove plus/minus buttons const plusButton = marketabilityPanel.querySelector('.bi-plus'); const minusButton = marketabilityPanel.querySelector('.bi-dash'); if (plusButton) plusButton.style.display = 'none'; if (minusButton) minusButton.style.display = 'none'; const comparisonStatBarContainer = document.createElement('div'); comparisonStatBarContainer.className = 'one-stat-bar comparison-bar'; const comparisonProgressBar = document.createElement('div'); comparisonProgressBar.className = 'one-stat-progress comparison-stat-progress'; comparisonProgressBar.style.width = `${marketability2}%`; comparisonProgressBar.style.backgroundColor = color2; comparisonStatBarContainer.append(comparisonProgressBar); barContainer.appendChild(comparisonStatBarContainer); const statNumberDiv = marketabilityPanel.querySelector('.stat-number'); const comparisonValueInput = document.createElement('input'); comparisonValueInput.setAttribute('readonly', 'readonly'); comparisonValueInput.className = 'custom-input-number comparison-stat-value'; comparisonValueInput.value = marketability2; const existingValueInput = statNumberDiv.querySelector('input.custom-input-number:not(.comparison-stat-value)'); existingValueInput.setAttribute('readonly', 'readonly'); if (parseInt(marketability2) > parseInt(marketability1)) { comparisonValueInput.classList.add(`comparing-tag`, `secondary`); existingValueInput.classList.remove("comparing-tag", "primary"); } else if (parseInt(marketability2) < parseInt(marketability1)) { comparisonValueInput.classList.remove("comparing-tag", "secondary"); existingValueInput.classList.add("comparing-tag", "primary"); } statNumberDiv.appendChild(comparisonValueInput); } } // Update Radar Chart const values1 = stats1.slice(0, 9); const values2 = stats2.slice(0, 9); //update the first dataset with the first driver color if (statsRadarChart) { updateStatsRadarData(values1, 0, cssVar(`--new-primary`)); } // Update Mentality const driver1 = document.querySelector('.normal-driver.clicked:not(.comparing-driver)'); const driver2 = document.querySelector('.normal-driver.clicked.comparing-driver'); //add the second dataset with the second driver color if (statsRadarChart) { let secondaryColor = color2; if (statsRadarChart.data.datasets.length < 2) { addDatasetToStatsRadarData(values2, secondaryColor, driver2.dataset.name.split(' ').pop()); } else { updateStatsRadarData(values2, 1, secondaryColor, driver2.dataset.name.split(' ').pop()); } } for (let i = 0; i < 3; i++) { if (driver1.dataset[`mentality${i}`] && driver2.dataset[`mentality${i}`]) { const mentality1 = driver1.dataset[`mentality${i}`]; const mentality2 = driver2.dataset[`mentality${i}`]; //remove previous comparison bar if exists const existingComparisonBar = document.getElementById(`mentality${i}`).parentNode.parentNode.querySelector('.comparison-bar'); if (existingComparisonBar) existingComparisonBar.remove(); const mentalityPanel = document.getElementById(`mentality${i}`).parentNode.parentNode; const barContainer = mentalityPanel.querySelector('.bar-container'); // Hide buttons const buttons = mentalityPanel.querySelectorAll('.bi-chevron-left, .bi-chevron-right'); buttons.forEach(btn => btn.style.display = 'none'); // Create and prepend the second mentality bar const comparisonBar = document.createElement('div'); comparisonBar.className = 'mentality-level-indicator comparison-bar'; for (let j = 0; j < 5; j++) { const level = document.createElement('div'); level.className = 'mentality-level'; if (j <= 4 - mentality2) { level.classList.add(mentality_dict[mentality2]); } comparisonBar.appendChild(level); } barContainer.prepend(comparisonBar); } } // Redesign Header const header = document.querySelector('.upper-section-stats'); const originalInfo = header.querySelector('.name-and-info'); const originalOvr = header.querySelector('.special-overall'); const originalAgeDetails = header.querySelector('#ageDetails'); // Clone and populate driver 2 info let clonedInfo = header.querySelector('.name-and-info.cloned'); if (!clonedInfo) { clonedInfo = originalInfo.cloneNode(true); clonedInfo.classList.add('cloned'); // header.querySelector("#ageDetails").classList.add("d-none"); header.querySelector("#numberDetails").classList.add("d-none"); header.querySelector("#availabilityDetails").classList.add("d-none"); header.appendChild(clonedInfo); } document.querySelectorAll(".hidable-separator").forEach(separator => { separator.classList.add("d-none"); }); //put second driver name clonedInfo.querySelector("#driverStatsTitle").innerText = `${driver2.dataset.name}`; clonedInfo.querySelector("#driverCode").innerText = `${driver2.dataset.driverCode}`; clonedInfo.querySelector('.driver-info-driver-flag').src = `https://flagsapi.com/${driver2.dataset.nationality}/flat/64.png`; clonedInfo.querySelector('.flag-text').textContent = inverted_countries_abreviations[driver2.dataset.nationality] || driver2.dataset.nationality; const driver2TeamId = Number(driver2.dataset.teamid); const clonedLogoImg = clonedInfo.querySelector('.driver-info-team-logo-img'); const clonedLogoMasked = clonedInfo.querySelector('.driver-info-team-logo-masked'); const logo2 = logos_disc[driver2TeamId] || logos_disc[0]; if (!clonedLogoImg || !clonedLogoMasked || !driver2TeamId || !logo2) { clonedLogoImg?.classList.add("d-none"); clonedLogoMasked?.classList.add("d-none"); } else if (driver2TeamId === 2) { clonedLogoImg.classList.add("d-none"); clonedLogoMasked.classList.remove("d-none"); } else { clonedLogoMasked.classList.add("d-none"); clonedLogoImg.classList.remove("d-none"); clonedLogoImg.src = logo2; } let teamName = combined_dict[driver2.dataset.teamid] || "Free Agent"; clonedInfo.querySelector('.team-text').textContent = teamName !== "Visa Cashapp RB" ? teamName : "VCARB"; // Clone and populate driver 2 overall let clonedOvr = header.querySelector('.special-overall.cloned'); if (!clonedOvr) { clonedOvr = originalOvr.cloneNode(true); clonedOvr.classList.add('cloned'); header.insertBefore(clonedOvr, clonedInfo); const separator = document.createElement('div'); separator.className = 'stats-header-separator cloned-separator'; header.insertBefore(separator, clonedInfo); } if (clonedOvr) clonedOvr.querySelector(".overall-holder").innerText = calculateOverall(driver2.dataset.stats, "driver"); let clonedAgeDetails = header.querySelector('#ageDetails.cloned'); if (originalAgeDetails && !clonedAgeDetails) { clonedAgeDetails = originalAgeDetails.cloneNode(true); clonedAgeDetails.classList.add('cloned'); header.insertBefore(clonedAgeDetails, clonedInfo); const separator = document.createElement('div'); separator.className = 'stats-header-separator cloned-separator'; header.insertBefore(separator, clonedInfo); document.querySelectorAll(".shorten-ret").forEach(elem => { elem.innerText = "Ret" }); } if (clonedAgeDetails) { clonedAgeDetails.querySelector('.actual-age').innerText = driver2.dataset.age; clonedAgeDetails.querySelector('.actual-retirement').innerText = driver2.dataset.retirement; } } ================================================ FILE: src/js/frontend/teamReplacements.js ================================================ import { Command } from "../backend/command.js"; export const names_configs = { "visarb": "VISA CASHAPP RB", "toyota": "TOYOTA", "hugo": "HUGO BOSS", "alphatauri": "ALPHA TAURI", "brawn": "BRAWN GP", "porsche": "PORSCHE", "alpine": "ALPINE", "renault": "RENAULT", "andretti": "ANDRETTI", "lotus": "LOTUS", "cadillac": "CADILLAC", "alfa": "ALFA ROMEO", "audi": "AUDI", "sauber": "SAUBER", "stake": "STAKE SAUBER", "williams": "WILLIAMS", "bmw": "BMW", "haas": "HAAS", "redbull": "RED BULL", "ford": "FORD", "aston": "ASTON MARTIN", "racingpoint": "RACING POINT", "jordan": "JORDAN" }; export const pretty_names = { "visarb": "Visa Cashapp RB", "toyota": "Toyota", "hugo": "Hugo Boss", "alphatauri": "Alpha Tauri", "brawn": "Brawn GP", "porsche": "Porsche", "alpine": "Alpine", "renault": "Renault", "andretti": "Andretti", "lotus": "Lotus", "cadillac": "Cadillac", "alfa": "Alfa Romeo", "audi": "Audi", "sauber": "Sauber", "stake": "Stake Sauber", "williams": "Williams", "bmw": "BMW", "haas": "Haas", "redbull": "Red Bull", "ford": "Ford", "aston": "Aston Martin", "racingpoint": "Racing Point", "jordan": "Jordan" }; export const abreviations_for_replacements = { "visarb": "VCARB", "toyota": "TOY", "hugo": "HUGO", "alphatauri": "AT", "brawn": "BGP", "porsche": "POR", "alpine": "ALP", "renault": "REN", "andretti": "AND", "lotus": "LOT", "cadillac": "CAD", "alfa": "ALFA", "audi": "AUDI", "sauber": "SAU", "stake": "STK", "williams": "WIL", "bmw": "BMW", "haas": "HA", "redbull": "RBR", "ford": "FOR", "aston": "AM", "racingpoint": "RP", "jordan": "JOR" }; export const logos_configs = { "visarb": "../assets/images/logos/visarb.png", "toyota": "../assets/images/logos/toyota.svg", "hugo": "../assets/images/logos/hugoboss.png", "alphatauri": "../assets/images/logos/alphatauri.png", "brawn": "../assets/images/logos/brawn.png", "porsche": "../assets/images/logos/porsche.png", "alpine": "../assets/images/logos/alpine.png", "renault": "../assets/images/logos/renault.png", "andretti": "../assets/images/logos/andretti.png", "lotus": "../assets/images/logos/lotus.png", "cadillac": "../assets/images/logos/cadillac.png", "alfa": "../assets/images/logos/alfaromeo.png", "audi": "../assets/images/logos/audi.png", "sauber": "../assets/images/logos/sauber.svg", "stake": "../assets/images/logos/kick.png", "williams": "../assets/images/logos/Williams_2026_logo.svg", "bmw": "../assets/images/logos/bmw.png", "haas": "../assets/images/logos/haas.png", "redbull": "../assets/images/logos/redbull.png", "ford": "../assets/images/logos/ford.png", "aston": "../assets/images/logos/astonMartin.png", "racingpoint": "../assets/images/logos/racingpoint.png", "jordan": "../assets/images/logos/jordan.png" }; export const logos_classes_configs = { "visarb": "visarblogo", "toyota": "toyotalogo", "hugo": "hugologo", "alphatauri": "alphataurilogo", "porsche": "porschelogo", "brawn": "brawnlogo", "alpine": "alpinelogo", "renault": "renaultlogo", "andretti": "andrettilogo", "lotus": "lotuslogo", "cadillac": "cadillaclogo", "alfa": "alfalogo", "audi": "audilogo", "sauber": "sauberlogo", "stake": "alfalogo", "williams": "williamslogo", "bmw": "bmwlogo", "redbull": "redbulllogo", "ford": "fordlogo", "aston": "astonlogo", "racingpoint": "racingpointlogo", "jordan": "jordanlogo", "haas": "haaslogo" }; function updateTeamMenuClass(selector, info) { document.querySelectorAll(selector).forEach(function (elem) { Array.from(elem.classList).forEach(function (cl) { if (cl.startsWith("changable-team-menu-")) { elem.classList.remove(cl); } }); elem.classList.add("changable-team-menu-" + info); }); } function updateTeamColors({ baseKey, info, primaryId, secondaryId, edit_colors_dict, change_css_variables }) { if (info !== baseKey) { let baseVarName = `--${baseKey}-primary`; let newVarName = `--${info}-primary`; change_css_variables(baseVarName, newVarName); let value = getComputedStyle(document.documentElement).getPropertyValue(newVarName).trim(); edit_colors_dict(primaryId, value); baseVarName = `--${baseKey}-secondary`; newVarName = `--${info}-secondary`; change_css_variables(baseVarName, newVarName); value = getComputedStyle(document.documentElement).getPropertyValue(newVarName).trim(); edit_colors_dict(secondaryId, value); baseVarName = `--${baseKey}-primary-transparent`; newVarName = `--${info}-primary-transparent`; change_css_variables(baseVarName, newVarName); baseVarName = `--${baseKey}-secondary-transparent`; newVarName = `--${info}-secondary-transparent`; change_css_variables(baseVarName, newVarName); return; } let baseVarName = `--${baseKey}-primary`; let newVarName = `--${baseKey}-original`; change_css_variables(baseVarName, newVarName); let value = getComputedStyle(document.documentElement).getPropertyValue(newVarName).trim(); edit_colors_dict(primaryId, value); baseVarName = `--${baseKey}-secondary`; newVarName = `--${baseKey}-secondary-original`; change_css_variables(baseVarName, newVarName); value = getComputedStyle(document.documentElement).getPropertyValue(newVarName).trim(); edit_colors_dict(secondaryId, value); baseVarName = `--${baseKey}-primary-transparent`; newVarName = `--${baseKey}-primary-transparent-original`; change_css_variables(baseVarName, newVarName); baseVarName = `--${baseKey}-secondary-transparent`; newVarName = `--${baseKey}-secondary-transparent-original`; change_css_variables(baseVarName, newVarName); } function replaceTeam(config, info, deps) { const { combined_dict, abreviations_dict, edit_colors_dict, change_css_variables } = deps; document.querySelector(config.buttonSelector).querySelector("button span").textContent = names_configs[info]; document.querySelector(config.buttonSelector).querySelector("button").dataset.value = info; combined_dict[config.teamId] = pretty_names[info]; abreviations_dict[config.teamId] = abreviations_for_replacements[info]; const command = new Command("updateCombinedDict", { teamID: config.teamId, newName: pretty_names[info] }); command.execute(); document.querySelectorAll(config.teamNameSelector).forEach(function (elem) { elem.dataset.teamshow = pretty_names[info]; }); config.updateNames(info); config.updateLogos(info); updateTeamColors({ baseKey: config.baseKey, info, primaryId: config.primaryColorId, secondaryId: config.secondaryColorId, edit_colors_dict, change_css_variables }); updateTeamMenuClass(config.teamMenuSelector, info); } export function createTeamReplacers(deps) { const alphaConfig = { buttonSelector: "#alphaTauriReplaceButton", teamId: 8, teamNameSelector: ".at-teamname", teamMenuSelector: ".team-menu-alphatauri-replace", baseKey: "alphatauri", primaryColorId: "80", secondaryColorId: "81", updateNames(info) { document.querySelectorAll(".at-name").forEach(function (elem) { let name = (info === "visarb" && !elem.classList.contains("complete")) ? "VCARB" : names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = (info === "visarb" && !elem.classList.contains("complete")) ? "VCARB" : pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { if (info !== "alphatauri") { document.querySelectorAll(".atlogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { let newElem; if (info === "porsche") { newElem = document.createElement("img"); newElem.src = logos_configs[info]; } else { newElem = document.createElement("div"); } newElem.className = elem.className; newElem.classList.remove("alphataurilogo", "toyotalogo", "hugologo", "porschelogo", "visarblogo", "ferrarilogo", "brawnlogo"); newElem.classList.add(logos_classes_configs[info]); elem.replaceWith(newElem); } if (elem.classList.contains("secondary")) { if (info !== "toyota") { elem.src = elem.src.slice(0, -4) + "2.png"; } } }); return; } document.querySelectorAll(".atlogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { elem.src = logos_configs[info]; elem.classList.remove("alphataurilogo"); elem.classList.remove("toyotalogo"); elem.classList.remove("hugologo"); elem.classList.remove("porschelogo"); elem.classList.remove("visarblogo"); elem.classList.remove("ferrarilogo"); elem.classList.remove("brawnlogo"); elem.classList.add("alphataurilogo"); } if (elem.classList.contains("secondary")) { elem.src = elem.src.slice(0, -4) + "2.png"; } }); } }; const alpineConfig = { buttonSelector: "#alpineReplaceButton", teamId: 5, teamNameSelector: ".al-teamname", teamMenuSelector: ".team-menu-alpine-replace", baseKey: "alpine", primaryColorId: "50", secondaryColorId: "51", updateNames(info) { document.querySelectorAll(".alpine-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { if (info === "cadillac") { document.querySelectorAll(".alpinelogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("alpinelogo", "andrettilogo", "renaultlogo", "lotuslogo"); elem.classList.add(logos_classes_configs[info]); } if (elem.classList.contains("secondary")) { elem.src = elem.src.slice(0, -4) + "2.png"; } }); return; } if (info !== "alpine") { document.querySelectorAll(".alpinelogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() === "img") { const newElem = document.createElement("div"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.classList.remove("alpinelogo"); elem.classList.remove("andrettilogo"); elem.classList.remove("renaultlogo"); elem.classList.remove("lotuslogo"); elem.classList.remove("cadillaclogo"); elem.classList.add(logos_classes_configs[info]); } if (elem.classList.contains("secondary")) { elem.src = elem.src.slice(0, -4) + "2.png"; } }); return; } document.querySelectorAll(".alpinelogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() === "img") { const newElem = document.createElement("div"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("alpinelogo"); elem.classList.remove("andrettilogo"); elem.classList.remove("renaultlogo"); elem.classList.remove("lotuslogo"); elem.classList.remove("cadillaclogo"); elem.classList.add("alpinelogo"); } if (elem.classList.contains("secondary")) { elem.src = elem.src.slice(0, -4) + "2.png"; } }); } }; const williamsConfig = { buttonSelector: "#williamsReplaceButton", teamId: 6, teamNameSelector: ".wi-teamname", teamMenuSelector: ".team-menu-williams-replace", baseKey: "williams", primaryColorId: "60", secondaryColorId: "61", updateNames(info) { document.querySelectorAll(".williams-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { document.querySelectorAll(".williamslogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { elem.classList.remove("williamslogo"); elem.classList.remove("bmwlogo"); elem.classList.add(logos_classes_configs[info]); // bmw logo is a png if (info === "bmw") { //create an img element to replace the div let newElem = document.createElement("img"); newElem.src = logos_configs[info]; newElem.className = elem.className; elem.replaceWith(newElem); } else { //if not bmw, make sure it's a div if (elem.tagName.toLowerCase() === "img") { let newElem = document.createElement("div"); newElem.className = elem.className; elem.replaceWith(newElem); } } } }); } }; const haasConfig = { buttonSelector: "#haasReplaceButton", teamId: 7, teamNameSelector: ".ha-teamname", teamMenuSelector: ".team-menu-haas-replace", baseKey: "haas", primaryColorId: "70", secondaryColorId: "71", updateNames(info) { document.querySelectorAll(".haas-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { document.querySelectorAll(".haaslogo-replace").forEach(function (elem) { if (elem.classList.contains("non-changable")) return; const isMaskLogo = info === "toyota"; if (isMaskLogo) { const newElem = document.createElement("div"); newElem.className = elem.className; newElem.classList.remove("haaslogo"); newElem.classList.remove("toyotalogo"); newElem.classList.add("toyotalogo"); elem.replaceWith(newElem); return; } if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("toyotalogo"); elem.classList.add(logos_classes_configs[info]); }); } }; const alfaConfig = { buttonSelector: "#alfaReplaceButton", teamId: 9, teamNameSelector: ".af-teamname", teamMenuSelector: ".team-menu-alfa-replace", baseKey: "alfa", primaryColorId: "90", secondaryColorId: "91", updateNames(info) { document.querySelectorAll(".alfa-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { if (info !== "alfa") { document.querySelectorAll(".alfalogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { const isMaskLogo = info === "sauber"; if (isMaskLogo) { const newElem = document.createElement("div"); newElem.className = elem.className; newElem.classList.remove("alfaromeologo"); newElem.classList.remove("audilogo"); newElem.classList.remove("sauberlogo"); newElem.classList.add(logos_classes_configs[info]); elem.replaceWith(newElem); return; } if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("alfaromeologo"); elem.classList.remove("audilogo"); elem.classList.remove("sauberlogo"); elem.classList.add(logos_classes_configs[info]); } }); return; } document.querySelectorAll(".alfalogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = "alfalogo-replace alfalogo"; newElem.src = logos_configs[info]; elem.replaceWith(newElem); return; } elem.src = logos_configs[info]; elem.className = "alfalogo-replace alfalogo"; } }); } }; const redbullConfig = { buttonSelector: "#redbullReplaceButton", teamId: 3, teamNameSelector: ".rb-teamname", teamMenuSelector: ".team-menu-redbull-replace", baseKey: "redbull", primaryColorId: "30", secondaryColorId: "31", updateNames(info) { document.querySelectorAll(".redbull-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { if (elem.classList.contains("complete") && info === "redbull") { name = "RED BULL"; } elem.textContent = name; } }); }, updateLogos(info) { document.querySelectorAll(".redbulllogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("redbulllogo", "fordlogo"); elem.classList.add(logos_classes_configs[info]); } }); } }; const astonConfig = { buttonSelector: "#astonReplaceButton", teamId: 10, teamNameSelector: ".as-teamname", teamMenuSelector: ".team-menu-aston-replace", baseKey: "aston", primaryColorId: "100", secondaryColorId: "101", updateNames(info) { document.querySelectorAll(".aston-name").forEach(function (elem) { let name = names_configs[info]; if (elem.parentElement.classList.contains("car-title")) { const match = elem.textContent.match(/^(.*?)\s+(\d+\s*-\s*#\d+)/); if (match) { name = pretty_names[info]; elem.textContent = `${name} ${match[2]}`; } } else { elem.textContent = name; } }); }, updateLogos(info) { document.querySelectorAll(".astonlogo-replace").forEach(function (elem) { if (!elem.classList.contains("non-changable")) { if (elem.tagName.toLowerCase() !== "img") { const newElem = document.createElement("img"); newElem.className = elem.className; elem.replaceWith(newElem); elem = newElem; } elem.src = logos_configs[info]; elem.classList.remove("astonlogo", "racingpointlogo", "jordanlogo"); elem.classList.add(logos_classes_configs[info]); } }); } }; return { replaceTeam, alphaTauriReplace(info) { replaceTeam(alphaConfig, info, deps); }, alpineReplace(info) { replaceTeam(alpineConfig, info, deps); }, williamsReplace(info) { replaceTeam(williamsConfig, info || "williams", deps); }, haasReplace(info) { replaceTeam(haasConfig, info || "haas", deps); }, alfaReplace(info) { replaceTeam(alfaConfig, info, deps); }, redbullReplace(info) { replaceTeam(redbullConfig, info, deps); }, astonReplace(info) { replaceTeam(astonConfig, info, deps); } }; } ================================================ FILE: src/js/frontend/teams.js ================================================ import { team_dict } from "./config"; import { Command } from "../backend/command.js"; import { manage_stat_bar } from "./stats"; import { attachHold } from "./renderer.js"; export let teamCod; let currYear; export let originalCostCap; export let longTermObj; const MAX_ARC_LENGTH = 212; /** * Listener for the team menu buttons */ document.querySelector("#teamMenu").querySelectorAll("a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector("#teamButton span").innerText = elem.querySelector(".team-menu-name").innerText; teamCod = elem.dataset.teamid; let data = { teamID: teamCod, } document.querySelector("#teamButton").classList.remove("open") const command = new Command("teamRequest", data); command.execute(); document.querySelector(".team-viewer").classList.remove("d-none") }) }) /** * Listener for the objective menu dropdown */ document.querySelector("#objectiveMenu").querySelectorAll("a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector(".objective-label").innerText = elem.textContent longTermObj = elem.id[elem.id.length - 1] }) }) /** * Helper function to add mousedown listener with auto increment/decrement */ function addContinuousListener(element, selector, incrementCallback, decrementCallback) { let intervalId; element.querySelectorAll(selector).forEach(function (elem) { elem.addEventListener('mousedown', function () { let input = this.parentNode.parentNode.querySelector("input"); if (this.classList.contains('bi-chevron-up') || this.classList.contains('bi-plus-lg')) { incrementCallback(input); intervalId = setInterval(() => { incrementCallback(input); }, 100); } else if (this.classList.contains('bi-chevron-down') || this.classList.contains('bi-dash-lg')) { decrementCallback(input); intervalId = setInterval(() => { decrementCallback(input); }, 100); } }); elem.addEventListener('mouseup', function () { clearInterval(intervalId); }); elem.addEventListener('mouseleave', function () { clearInterval(intervalId); }); }); } attachHold(document.querySelector("#objAndYear .input-and-buttons .bi-plus"), document.querySelector("#longTermInput"), +1, { min: 2023, max: 2023 + 1000 }); attachHold(document.querySelector("#objAndYear .input-and-buttons .bi-dash"), document.querySelector("#longTermInput"), -1, { min: 2023, max: 2023 + 1000 }); attachHold(document.querySelector("#seasonObjective .bi-plus"), document.querySelector("#seasonObjectiveInput"), -1, { min: 1, max: 10 }); attachHold(document.querySelector("#seasonObjective .bi-dash"), document.querySelector("#seasonObjectiveInput"), +1, { min: 1, max: 10 }); attachHold(document.querySelector("#confidence .bi-plus"), document.querySelector("#confidence input"), +5, { min: 0, max: 100 }); attachHold(document.querySelector("#confidence .bi-dash"), document.querySelector("#confidence input"), -5, { min: 0, max: 100 }); attachHold(document.querySelector("#costCap .bi-plus"), document.querySelector("#costCap input"), +100000, { min: -9999999999, max: 1000000000, format: (val) => val.toLocaleString("en-US") }); attachHold(document.querySelector("#costCap .bi-dash"), document.querySelector("#costCap input"), -100000, { min: -9999999999, max: 1000000000, format: (val) => val.toLocaleString("en-US") }); attachHold( document.querySelector("#teamBudget .bi-plus"), document.querySelector("#teamBudget input"), +100000, { min: -9999999999, max: 1000000000, format: (val) => val.toLocaleString("en-US") } ); attachHold(document.querySelector("#teamBudget .bi-dash"), document.querySelector("#teamBudget input"), -100000, { min: -9999999999, max: 1000000000, format: (val) => val.toLocaleString("en-US") } ); document.querySelectorAll(".gauge-and-buttons .bi-plus").forEach(btn => { const wrapper = btn.closest('.gauge-and-buttons'); const textSpan = wrapper.querySelector('.gauge-indicator'); const gaugeContainer = wrapper.querySelector('.gauge-container'); attachHold(btn, textSpan, +1, { min: 0, max: 100, format: v => v + '%', // Formato con porcentaje onChange: (val) => { updateGaugeVisual(gaugeContainer, val); } }); }); document.querySelectorAll(".gauge-and-buttons .bi-dash").forEach(btn => { const wrapper = btn.closest('.gauge-and-buttons'); const textSpan = wrapper.querySelector('.gauge-indicator'); const gaugeContainer = wrapper.querySelector('.gauge-container'); attachHold(btn, textSpan, -1, { min: 0, max: 100, format: v => v + '%', onChange: (val) => { updateGaugeVisual(gaugeContainer, val); } }); }); function updateGaugeVisual(container, value) { container.style.setProperty('--perc', value); //if value is 100 set font-size to 12px const textSpan = container.querySelector('.gauge-indicator'); if (value === 100) { textSpan.style.fontSize = '10px'; } else { textSpan.style.fontSize = ''; } } /** * Fills the level for each facility * @param {object} teamData info of the team facilities */ export function fillLevels(teamData) { teamData.slice(0, 15).forEach(function (elem) { let num = elem[0]; let level = num % 10; let facilityID = Math.floor(num / 10); let facility = document.querySelector("#facility" + facilityID) let indicator = facility.querySelector('.facility-level-indicator') let gaugeElement = facility.querySelector('.gauge-container'); if (gaugeElement) { let percentage = parseInt(elem[1] * 100); gaugeElement.style.setProperty('--perc', percentage); let gaugeText = gaugeElement.querySelector('.gauge-indicator'); if (gaugeText) { gaugeText.innerText = percentage + '%'; } if (percentage === 100) { gaugeText.style.fontSize = '10px'; } else { gaugeText.style.fontSize = ''; } } indicator.dataset.value = level let value = level let levels = indicator.querySelectorAll('.level'); for (let i = 0; i < 5; i++) { levels[i].className = "level" if (i <= value - 1) { levels[i].classList.add(team_dict[teamCod] + 'activated'); } } }) document.querySelector("#seasonObjectiveInput").value = teamData[16] document.querySelector("#longTermObj" + teamData[17][0]).click() document.querySelector("#longTermInput").value = teamData[17][1] document.querySelector("#teamBudgetInput").value = teamData[18].toLocaleString("en-US") document.querySelector("#costCapInput").value = Math.abs(teamData[19][0]).toLocaleString("en-US") manageConfidence(teamData[20]) document.querySelector("#confidenceInput").value = teamData[20] currYear = teamData[21] originalCostCap = Math.abs(teamData[19][0]) for (let key in teamData[22]) { let pitCrewStat = document.querySelector(`.pit-crew-details .one-stat-panel[data-crewStat='${key}']`); let input = pitCrewStat.querySelector("input"); let value = Math.round(teamData[22][key]); if (key === "38") { value = value / 10; } input.value = value + "%"; let bar = pitCrewStat.querySelector(".one-stat-progress"); bar.style.width = value + "%"; } let engineManufacturer = teamData[23]; document.querySelector(`#engineMenu a[data-engine='${engineManufacturer}']`).click(); let bars = document.querySelector(".pit-crew-details").querySelectorAll(".one-stat-progress"); bars.forEach(function (elem) { elem.classList = "one-stat-progress " + team_dict[teamCod] + "bar-primary"; }) } document.querySelectorAll("#engineMenu a").forEach(function (elem) { elem.addEventListener("click", function () { let engineiD = elem.dataset.engine; let engine = elem.innerText; document.querySelector("#engineLabel").innerText = engine; document.querySelector("#engineButton").dataset.value = engineiD; }) }) /** * Resets the view */ export function resetTeamEditing() { document.querySelector(".team-viewer").classList.add("d-none"); teamCod = null; document.querySelector("#teamButton").innerText = "Team"; } function updatePitStat(input, increment) { let actual = input.value.split("%")[0]; let val = parseInt(actual) + increment; if (val > 100) val = 100; if (val < 0) val = 0; input.value = val + "%"; manage_stat_bar(input, val); } document.querySelector(".pit-crew-details").querySelectorAll(".bi-plus").forEach(function (elem) { // Guardamos referencia al input una sola vez para no buscarlo a cada clic const input = elem.parentNode.querySelector("input"); attachHold(elem, input, +1, { min: 0, max: 100, // 1. Añadimos el símbolo % visualmente format: (v) => v + '%', // 2. Corregimos el onChange para que reciba el valor nuevo (val) onChange: function (val) { manage_stat_bar(input, val); } }); }); // Configuración para el botón - (DASH) document.querySelector(".pit-crew-details").querySelectorAll(".bi-dash").forEach(function (elem) { const input = elem.parentNode.querySelector("input"); attachHold(elem, input, -1, { min: 0, max: 100, format: (v) => v + '%', onChange: function (val) { manage_stat_bar(input, val); } }); }); /** * Manages state of blocking div for confidence * @param {Number} data Confidence number. If -1, blocking div is activated */ function manageConfidence(data) { if (Number(data[0]) === -1) { document.querySelector("#confidence").classList.add("d-none") } else { document.querySelector("#confidence").classList.remove("d-none") } } /** * Listeners for the level indicators for each facility */ document.querySelector("#edit_teams").querySelectorAll(".bi-chevron-right").forEach(function (elem) { elem.addEventListener("click", function () { let indicator = elem.parentNode.querySelector(".facility-level-indicator") let value = parseInt(indicator.getAttribute('data-value')) + 1; if (value > 5) { value = 5 } indicator.setAttribute('data-value', value); let levels = indicator.querySelectorAll('.level'); if (value <= levels.length) { levels[value - 1].classList.add(team_dict[teamCod] + 'activated'); } }) }) document.querySelector("#edit_teams").querySelectorAll(".bi-chevron-left").forEach(function (elem) { elem.addEventListener("click", function () { let indicator = elem.parentNode.querySelector(".facility-level-indicator") let value = parseInt(indicator.getAttribute('data-value')) - 1; if (value < 0) { value = 0 } indicator.setAttribute('data-value', value); let levels = indicator.querySelectorAll('.level'); if (value < levels.length) { levels[value].className = "level" } }) }) /** * Collects the data for each facility * @returns array with tuples for each facility */ export function gather_team_data() { let facilities = document.getElementsByClassName('facility'); let result = []; for (let i = 0; i < facilities.length; i++) { let facility = facilities[i]; let id = facility.id.match(/\d+$/)[0]; // Extrae el número al final del id let levelIndicator = facility.getElementsByClassName('facility-level-indicator')[0]; let level = levelIndicator.getAttribute('data-value'); let number = id + level; // Compone el número concatenando los strings let condition = facility.querySelector('.gauge-indicator').innerText.split("%")[0] / 100; result.push([number, condition]); // Añade la tupla a la lista } return result } export function gather_pit_crew() { let pitCrewStats = document.querySelectorAll(".pit-crew-details .one-stat-panel"); let result = {}; pitCrewStats.forEach(function (elem) { let key = elem.dataset.crewstat; let value = elem.querySelector("input").value.split("%")[0]; if (key === "38") { value = value * 10; } result[key] = value; }); return result; } ================================================ FILE: src/js/frontend/transfers.js ================================================ import { staff_pics, team_dict, combined_dict, staff_positions, typeStaff_dict, f1_teams, f2_teams, f3_teams, inverted_dict, getUpdatedName, logos_disc } from "./config"; import { attachHold, game_version, make_name_prettier } from "./renderer"; import bootstrap from "bootstrap/dist/js/bootstrap.bundle.min.js"; import interact from 'interactjs'; import { Command } from "../backend/command.js"; const myModal = new bootstrap.Modal(document.getElementById('contractModal')); const raceBonusAmt = document.getElementById("raceBonusAmt"); const raceBonusPos = document.getElementById("raceBonusPos"); const freeDriversPill = document.getElementById("freepill"); const f2DriversPill = document.getElementById("F2pill"); const f3DriversPill = document.getElementById("F3pill"); export const freeDriversDiv = document.getElementById("free-drivers"); const freeStaffDiv = document.getElementById("free-staff"); const f2DriversDiv = document.getElementById("f2-drivers"); const f3DriversDiv = document.getElementById("f3-drivers"); const clearIcon = document.querySelector("#filterTransfersContainer .bi-x"); let freeDriverItems = [] let t; const autoContractToggle = document.getElementById("autoContractToggle") const lineupsViewButton = document.getElementById("lineupsViewButton"); const transfersMainLayout = document.getElementById("transfersMainLayout"); const lineupsView = document.getElementById("lineupsView"); const lineupsCircle = document.getElementById("lineupsCircle"); const lineupsSeasonPillsTop = document.getElementById("lineupsSeasonPillsTop"); const lineupsCurrentPill = document.getElementById("lineupsCurrentPill"); const lineupsNextPill = document.getElementById("lineupsNextPill"); const divsArray = [freeDriversDiv, f2DriversDiv, f3DriversDiv] let originalParent; let destinationParent; let draggable; let teamDestiniy; let teamOrigin; let posInTeam; let modalType; let driverEditingID; let driverEditingName; let driverEditingOfficialDriver = false; let driver1; let driver2; let originalTeamId export let currentSeason; let lineupsData = null; let lineupsSeasonType = "current"; let juniorTeamIdActive = -1; let juniorTeamDrivers = []; let juniorContractDirty = false; export function setCurrentSeason(season) { currentSeason = season } let name_dict = { 'ferrari': "Ferrari", 'mclaren': "McLaren", 'redbull': "Red Bull", 'merc': "Mercedes", 'alpine': "Alpine", 'williams': "Williams", 'haas': "Haas", 'alphatauri': "Alpha Tauri", 'alfaromeo': "Alfa Romeo", 'astonmartin': "Aston Martin", "F2": "F2", "F3": "F3", "custom": "Custom Team" } //custom team name changes so this dict stays here /** * Removes all the drivers from teams and categories */ export function remove_drivers(staffOnly = false) { if (!staffOnly) { document.querySelectorAll('.driver-space').forEach(item => { item.innerHTML = "" }); document.querySelectorAll('.affiliates-space').forEach(item => { item.innerHTML = "" }); freeDriversDiv.innerHTML = "" } document.querySelectorAll('.staff-space').forEach(item => { item.innerHTML = "" }); freeStaffDiv.innerHTML = "" } export function insert_space(str) { return str.replace(/([A-Z])/g, ' $1').trim(); } export function format_name(fullName, nameSplitted, spanName, spanLastName, onlyLastWordLastName = false) { const nameArray = String(fullName || "").split(" ").filter(Boolean); const firstName = nameArray[0] || ""; const lastName = nameArray[nameArray.length - 1] || ""; const middleNames = nameArray.slice(1, -1); if (String(fullName || "").length > 17) { if (insert_space(firstName).includes(" ")) { let splitName = insert_space(firstName).split(" "); spanName.textContent = splitName[0][0] + ". " + splitName[1]; } else { spanName.textContent = firstName[0] + "."; } if (onlyLastWordLastName && middleNames.length > 0) { spanName.textContent = spanName.textContent + " " + middleNames.map(n => insert_space(n)).join(" "); } spanName.textContent = spanName.textContent + " "; spanLastName.textContent = (onlyLastWordLastName ? lastName : nameArray.slice(1).join(" ")).toUpperCase(); } else { const parts = Array.isArray(nameSplitted) ? nameSplitted.filter(Boolean) : []; if (onlyLastWordLastName && parts.length > 1) { spanName.textContent = parts.slice(0, -1).map(p => insert_space(p)).join(" ").trim() + " "; spanLastName.textContent = String(parts[parts.length - 1] || "").toUpperCase(); } else { spanName.textContent = insert_space(parts[0] || "") + " "; spanLastName.textContent = parts.slice(1).join(" ").toUpperCase(); } } } /** * Places all drivers in their respective team, category etc * @param {Object} driversArray List of drivers */ export function place_drivers(driversArray) { console.log("DRIVERS ARRAY", driversArray) let divPosition; driversArray.forEach((driver) => { let newDiv = document.createElement("div"); newDiv.className = "col free-driver"; newDiv.dataset.driverid = driver[1]; newDiv.dataset.teamid = driver[2]; let name = driver[0].split(" ") let nameContainer = document.createElement("div") nameContainer.className = "name-container" let spanName = document.createElement("span") let spanLastName = document.createElement("span") format_name(driver[0], name, spanName, spanLastName) spanLastName.classList.add("bold-font") nameContainer.appendChild(spanName) nameContainer.appendChild(spanLastName) if (driver["team_junior"] && driver["team_junior"].teamId !== -1) { add_junior_formula_logo(newDiv, driver["team_junior"]) } newDiv.appendChild(nameContainer) newDiv.classList.add(team_dict[driver[2]] + "-transparent") if (driver["team_future"].teamId !== -1) { add_future_team_noti(newDiv, driver["team_future"]) } newDiv.dataset.futureteam = driver["team_future"].teamId manageColor(newDiv, spanLastName) divPosition = "free-drivers" let position = driver[3] if (position >= 3) { position = 3 } addIcon(newDiv) add_edit_container(newDiv) if (driver[2] > 0 && driver[2] <= 10 || driver[2] === 32) { divPosition = team_dict[driver[2]] + position; } document.getElementById(divPosition).appendChild(newDiv) }) document.querySelectorAll(".affiliates-and-arrows").forEach(updateAffiliateArrows) } function add_edit_container(div) { let edit_container = document.createElement("div") edit_container.className = "edit-container" let numbersicon = document.createElement("i") numbersicon.className = "bi bi bi-123" let pencilicon = document.createElement("i") pencilicon.className = "bi bi-pencil-fill" edit_container.appendChild(pencilicon) edit_container.appendChild(numbersicon) div.appendChild(edit_container) edit_container.addEventListener("click", function () { let id = div.dataset.driverid document.getElementById("statspill").click() let edit_stats_div = document.querySelector(`.normal-driver[data-driverid="${id}"]`) let typeStaff = typeStaff_dict[edit_stats_div.dataset.type] let menuClick = document.querySelector(`#staffMenu a[data-list="${typeStaff}"]`) menuClick.click() edit_stats_div.click() edit_stats_div.scrollIntoView({ behavior: "smooth", block: "center" }) }) } export function update_name(driverID, name) { let freeDiv = document.querySelector(`.free-driver[data-driverid='${driverID}']`) let normalDiv = document.querySelector(`.normal-driver[data-driverid='${driverID}']`) let nameContainer = freeDiv.querySelector(".name-container") let nameArray = name.split(" ") let new_name = nameArray[0] let new_surname = nameArray.slice(1).join(" ").toUpperCase() let firstNameContainer = nameContainer.childNodes[0] let lastNameContainer = nameContainer.querySelector(".bold-font") firstNameContainer.textContent = new_name lastNameContainer.textContent = new_surname firstNameContainer = normalDiv.childNodes[0].childNodes[0] lastNameContainer = normalDiv.childNodes[0].querySelector(".bold-font") firstNameContainer.textContent = new_name + " " lastNameContainer.textContent = new_surname normalDiv.dataset.name = name } export function sortList(divID) { let container = document.getElementById(divID); let divs = Array.from(container.querySelectorAll('.free-driver')); let compareFunction = (a, b) => { let futureTeamA = parseInt(a.dataset.futureteam); let futureTeamB = parseInt(b.dataset.futureteam); if (futureTeamA > futureTeamB) return -1; if (futureTeamA < futureTeamB) return 1; let textA = a.firstElementChild.textContent.toLowerCase(); let textB = b.firstElementChild.textContent.toLowerCase(); return textA.localeCompare(textB); }; divs.sort(compareFunction); container.innerHTML = ''; divs.forEach(div => container.appendChild(div)); } export function place_staff(staffArray) { let divPosition; staffArray.forEach((staff) => { let newDiv = document.createElement("div"); newDiv.className = "col free-driver"; newDiv.dataset.driverid = staff[1]; newDiv.dataset.teamid = staff[2]; let name = staff[0].split(" ") let spanName = document.createElement("span") let spanLastName = document.createElement("span") let marqueeContainer = document.createElement("div") marqueeContainer.className = "marquee-wrapper" let nameContainer = document.createElement("div") nameContainer.className = "name-container" spanName.textContent = insert_space(name[0]) + " " spanLastName.textContent = name.slice(1).join(" ").toUpperCase() spanLastName.classList.add("bold-font") let staffLogo = document.createElement("img") let position = staff[3] staffLogo.src = staff_pics[position] staffLogo.className = "staff-logo" newDiv.appendChild(staffLogo) nameContainer.appendChild(spanName) nameContainer.appendChild(spanLastName) marqueeContainer.appendChild(nameContainer) newDiv.appendChild(marqueeContainer) newDiv.classList.add(team_dict[staff[2]] + "-transparent") if (staff["team_future"].teamId !== -1) { add_future_team_noti(newDiv, staff["team_future"]) } newDiv.dataset.futureteam = staff["team_future"].teamId manageColor(newDiv, spanLastName) // if (staff[4] === 1) { // addUnRetireIcon(newDiv) // } divPosition = "free-staff" let staff_position = staff_positions[position] newDiv.dataset.type = staff_position staffLogo.classList.add(staff_position + "-border") addIcon(newDiv) add_edit_container(newDiv) if (staff[2] > 0 && staff[2] <= 10 || staff[2] === 32) { let teamDiv = document.querySelector(`.staff-section[data-teamid='${staff[2]}']`) if (position !== 2) { teamDiv.querySelector(`[data-type='${staff_position}']`).appendChild(newDiv) } else { let engineer_1_has_child = teamDiv.querySelector(`[data-type='${staff_position}'][data-pos='1']`).childElementCount if (engineer_1_has_child === 0) { teamDiv.querySelector(`[data-type='${staff_position}'][data-pos='1']`).appendChild(newDiv) } else { teamDiv.querySelector(`[data-type='${staff_position}'][data-pos='2']`).appendChild(newDiv) } } } else { document.getElementById(divPosition).appendChild(newDiv) } }) } export function initFreeDriversElems() { freeDriverItems = [ ...document.querySelectorAll("#free-drivers .free-driver"), ...document.querySelectorAll("#free-staff .free-driver"), ].map(el => { const first = el.children[0]?.textContent || ""; const last = el.children[1]?.textContent || ""; const full = (first + last).toLowerCase(); return { el, name: full }; }); } document.querySelectorAll("#stafftransfersMenu a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector("#staffTransfersDropdown span.dropdown-label").innerText = elem.innerText; let value = elem.dataset.value; document.querySelector("#staffTransfersDropdown").dataset.value = value; manage_staff_drivers(value) }) }) function manage_staff_drivers(value) { if (value === "drivers") { document.getElementById("free-drivers").classList.remove("d-none") document.getElementById("free-staff").classList.add("d-none") document.querySelectorAll(".drivers-section").forEach(function (elem) { elem.classList.remove("d-none") }) document.querySelectorAll(".staff-section").forEach(function (elem) { elem.classList.add("d-none") }) } else { document.getElementById("free-drivers").classList.add("d-none") document.getElementById("free-staff").classList.remove("d-none") document.querySelectorAll(".drivers-section").forEach(function (elem) { elem.classList.add("d-none") }) document.querySelectorAll(".staff-section").forEach(function (elem) { elem.classList.remove("d-none") }) } } function add_future_team_noti(driverDiv, teamInfo) { console.log("ADDING NOTI FOR FUTURE TEAM ", teamInfo) let notiDiv = document.createElement("div") notiDiv.className = `future-contract-noti noti-${team_dict[teamInfo.teamId]}${teamInfo.posInTeam > 2 ? "-affiliate" : ""}` driverDiv.appendChild(notiDiv) } function add_junior_formula_logo(driverDiv, juniorInfo) { let imgContainer = document.createElement("div") imgContainer.className = "junior-formula-logo" let img = document.createElement("img") img.src = logos_disc[juniorInfo.teamId] if (f2_teams.includes(juniorInfo.teamId)) { imgContainer.classList.add("f2-team") } else if (f3_teams.includes(juniorInfo.teamId)) { imgContainer.classList.add("f3-team") } img.dataset.juniorTeamId = juniorInfo.teamId imgContainer.appendChild(img) driverDiv.appendChild(imgContainer) } const affiliatesScrollDisabledClass = "affiliates-scroll-disabled" const affiliatesScrollEpsilon = 2 function getDirectAffiliateDrivers(affiliatesDiv) { return Array.from(affiliatesDiv.children).filter(child => child.classList?.contains("free-driver")) } function getAffiliateScrollStep(affiliatesDiv) { const items = getDirectAffiliateDrivers(affiliatesDiv) if (items.length >= 2) { const step = items[1].offsetLeft - items[0].offsetLeft return step > 0 ? step : 0 } if (items.length === 1) { const style = window.getComputedStyle(items[0]) const marginLeft = parseFloat(style.marginLeft) || 0 const marginRight = parseFloat(style.marginRight) || 0 return items[0].getBoundingClientRect().width + marginLeft + marginRight } return 0 } function clamp(number, min, max) { return Math.min(max, Math.max(min, number)) } function updateAffiliateArrows(wrapper) { const affiliatesDiv = wrapper.querySelector(".affiliates-space") const leftArrow = wrapper.querySelector(".bi-chevron-left") const rightArrow = wrapper.querySelector(".bi-chevron-right") if (!affiliatesDiv || !leftArrow || !rightArrow) { return } const items = getDirectAffiliateDrivers(affiliatesDiv) const shouldShowArrows = items.length > 0 leftArrow.classList.toggle("d-none", !shouldShowArrows) rightArrow.classList.toggle("d-none", !shouldShowArrows) if (!shouldShowArrows) { leftArrow.classList.remove(affiliatesScrollDisabledClass) rightArrow.classList.remove(affiliatesScrollDisabledClass) return } const maxScrollLeft = Math.max(0, affiliatesDiv.scrollWidth - affiliatesDiv.clientWidth) if (affiliatesDiv.scrollLeft > maxScrollLeft) { affiliatesDiv.scrollLeft = Math.max(0, maxScrollLeft) } if (affiliatesDiv.clientWidth <= 0) { leftArrow.classList.remove(affiliatesScrollDisabledClass) rightArrow.classList.remove(affiliatesScrollDisabledClass) return } const hasOverflow = maxScrollLeft > affiliatesScrollEpsilon const canScrollLeft = hasOverflow && affiliatesDiv.scrollLeft > affiliatesScrollEpsilon const canScrollRight = hasOverflow && affiliatesDiv.scrollLeft < (maxScrollLeft - affiliatesScrollEpsilon) leftArrow.classList.toggle(affiliatesScrollDisabledClass, !canScrollLeft) rightArrow.classList.toggle(affiliatesScrollDisabledClass, !canScrollRight) } function setupAffiliateScroller(wrapper) { const affiliatesDiv = wrapper.querySelector(".affiliates-space") const leftArrow = wrapper.querySelector(".bi-chevron-left") const rightArrow = wrapper.querySelector(".bi-chevron-right") if (!affiliatesDiv || !leftArrow || !rightArrow) { return } leftArrow.addEventListener("click", function () { const step = getAffiliateScrollStep(affiliatesDiv) if (step <= 0) return const maxScrollLeft = affiliatesDiv.scrollWidth - affiliatesDiv.clientWidth const target = clamp(affiliatesDiv.scrollLeft - step, 0, maxScrollLeft) affiliatesDiv.scrollTo({ left: target, behavior: "smooth" }) }) rightArrow.addEventListener("click", function () { const step = getAffiliateScrollStep(affiliatesDiv) if (step <= 0) return const maxScrollLeft = affiliatesDiv.scrollWidth - affiliatesDiv.clientWidth const target = clamp(affiliatesDiv.scrollLeft + step, 0, maxScrollLeft) affiliatesDiv.scrollTo({ left: target, behavior: "smooth" }) }) affiliatesDiv.addEventListener("scroll", function () { updateAffiliateArrows(wrapper) }, { passive: true }) const observer = new MutationObserver(function () { updateAffiliateArrows(wrapper) }) observer.observe(affiliatesDiv, { childList: true }) updateAffiliateArrows(wrapper) } const affiliateScrollers = Array.from(document.querySelectorAll(".affiliates-and-arrows")) affiliateScrollers.forEach(setupAffiliateScroller) window.addEventListener("resize", function () { affiliateScrollers.forEach(updateAffiliateArrows) }) /** * Updates the color from the div depending on the team, both in contract and stats view * @param {div} div div from the driver */ function updateColor(div) { let surnameDiv = div.querySelector(".bold-font") surnameDiv.className = "bold-font" manageColor(div, surnameDiv) let statsDiv = document.querySelector('.normal-driver[data-driverid="' + div.dataset.driverid + '"]') statsDiv.dataset.teamid = div.dataset.teamid surnameDiv = statsDiv.querySelector(".surname") surnameDiv.className = "bold-font surname" manageColor(statsDiv, surnameDiv) div.className = "colr free-driver " + team_dict[div.dataset.teamid] + "-transparent" statsDiv.className = "colr normal-driver " + team_dict[div.dataset.teamid] + "-transparent" } /** * Manages the color depending on the team * @param {div} div div from the driver * @param {span} lastName the lastname span from the driver */ export function manageColor(div, lastName) { if (div.dataset.teamid != 0) { let colorClass = team_dict[div.dataset.teamid] + "font" lastName.classList.add(colorClass) } } /** * Adds the edit icon * @param {div} div div from the driver that is going to add the icon into */ function addIcon(div) { let iconDiv = document.createElement("div"); iconDiv.className = "custom-icon" let iconElement = document.createElement("i"); iconElement.className = "bi bi-pencil-square"; iconListener(iconElement) iconDiv.appendChild(iconElement) div.appendChild(iconDiv) } /** * Adds the eventlistener for one icon * @param {div} icon div from the icon */ function iconListener(icon) { icon.addEventListener("click", function () { document.querySelector("#juniorContractDropdown").classList.remove("d-none") document.querySelector("#contractPills").classList.remove("d-none") modalType = "edit" document.getElementById("contractModalTitle").innerText = icon.parentNode.parentNode.innerText.replace(/\n/g, ' ') + "'s"; fetchContracts(icon.parentNode.parentNode) let space = icon.parentNode.parentNode.parentNode //officialDriver = space has an id that ends with 1 or 2 driverEditingOfficialDriver = space.id.endsWith("1") || space.id.endsWith("2") if (space.classList.contains("driver-space") || space.classList.contains("affiliates-space") || (space.id === "free-drivers" && (f2_teams.includes(parseInt(icon.parentNode.parentNode.dataset.teamid)) || f3_teams.includes(parseInt(icon.parentNode.parentNode.dataset.teamid))))) { manage_modal_driver_staff("driver") } else if (space.classList.contains("staff-space") || (space.id === "free-staff" && (f2_teams.includes(parseInt(icon.parentNode.parentNode.dataset.teamid)) || f3_teams.includes(parseInt(icon.parentNode.parentNode.dataset.teamid))))) { if (event.target.parentNode.parentNode.dataset.type === "race-engineer") { manage_modal_driver_staff("race-engineer") } else { manage_modal_driver_staff("staff") } } else if (space.id === "free-drivers") { manage_modal_driver_staff("free-driver") } else if (space.id === "free-staff") { if (event.target.parentNode.parentNode.dataset.type === "race-engineer") { manage_modal_driver_staff("free-race-engineer") } else { manage_modal_driver_staff("free-staff") } } myModal.show() }) } function manage_modal_driver_staff(type) { if (type === "staff" || type === "race-engineer") { document.getElementById("currentContractTitle").classList.remove("d-none") document.getElementById("currentContractOptions").classList.remove("d-none") document.querySelectorAll(".driver-only").forEach(function (elem) { let input = elem.querySelector("input") input.disabled = true input.classList.add("disabled") let buttons = elem.querySelectorAll("i") buttons.forEach(function (button) { button.classList.add("disabled") }) }) } else if (type === "driver") { document.getElementById("currentContractTitle").classList.remove("d-none") document.getElementById("currentContractOptions").classList.remove("d-none") document.querySelectorAll(".driver-only").forEach(function (elem) { let input = elem.querySelector("input") input.disabled = false input.classList.remove("disabled") let buttons = elem.querySelectorAll("i") buttons.forEach(function (button) { button.classList.remove("disabled") }) }) let positionInput = document.querySelector("#positionInput input") positionInput.max = 999 } else if (type === "free-driver") { document.querySelectorAll(".driver-only").forEach(function (elem) { let input = elem.querySelector("input") input.disabled = false input.classList.remove("disabled") let buttons = elem.querySelectorAll("i") buttons.forEach(function (button) { button.classList.remove("disabled") }) }) let positionInput = document.querySelector("#positionInput input") positionInput.max = 999 document.getElementById("currentContractOptions").classList.add("d-none") document.getElementById("futureContractOptions").classList.add("d-none") document.getElementById("futureContractTitle").classList.add("d-none") document.getElementById("currentContractTitle").classList.add("d-none") document.querySelector(".add-contract").classList.remove("d-none") } else if (type === "free-staff") { document.querySelectorAll(".driver-only").forEach(function (elem) { let input = elem.querySelector("input") input.disabled = true input.classList.add("disabled") let buttons = elem.querySelectorAll("i") buttons.forEach(function (button) { button.classList.add("disabled") }) }) let positionInput = document.querySelector("#positionInput input") positionInput.disabled = true let buttons = document.querySelectorAll("#positionInput i") buttons.forEach(function (button) { button.classList.add("disabled") }) document.getElementById("currentContractOptions").classList.add("d-none") document.getElementById("futureContractOptions").classList.add("d-none") document.getElementById("futureContractTitle").classList.add("d-none") document.getElementById("currentContractTitle").classList.add("d-none") document.querySelector(".add-contract").classList.remove("d-none") } else if (type === "free-race-engineer") { document.querySelectorAll(".driver-only").forEach(function (elem) { let input = elem.querySelector("input") input.disabled = true input.classList.add("disabled") let buttons = elem.querySelectorAll("i") buttons.forEach(function (button) { button.classList.add("disabled") }) }) let input = document.querySelector("#positionInput input") let buttons = document.querySelectorAll("#positionInput i") input.disabled = false input.max = 2 input.classList.remove("disabled") buttons.forEach(function (button) { button.classList.remove("disabled") }) document.getElementById("currentContractOptions").classList.add("d-none") document.getElementById("futureContractOptions").classList.add("d-none") document.getElementById("futureContractTitle").classList.add("d-none") document.getElementById("currentContractTitle").classList.add("d-none") document.querySelector(".add-contract").classList.remove("d-none") } if (type === "race-engineer") { let input = document.querySelector("#positionInput input") let buttons = document.querySelectorAll("#positionInput i") input.disabled = false input.max = 2 input.classList.remove("disabled") buttons.forEach(function (button) { button.classList.remove("disabled") }) } } /** * Places all the values for the modal that just openend * @param {Object} info values for the contract modal that just opened */ export function manage_modal(info) { if (info[0] !== null) { let teamID; if (info[0][5] <= 10 || info[0][5] === 32) { teamID = info[0][5] } else if (f2_teams.includes(info[0][5])) { teamID = 33 } else if (f3_teams.includes(info[0][5])) { teamID = 34 } document.getElementById("currentContract").innerText = getUpdatedName(info[0][5]).toUpperCase() document.getElementById("currentContract").className = "team-contract engine-" + team_dict[teamID] document.getElementById("yearInput").dataset.maxYear = info[4] document.getElementById("yearInput").min = info[4] document.getElementById("yearInputFuture").min = info[4] + 1 document.querySelector("#currentContractOptions").querySelectorAll(".contract-modal-input").forEach(function (elem, index) { if (elem.id === "salaryInput" || elem.id === "signBonusInput" || elem.id === "raceBonusAmt") { elem.value = info[0][index].toLocaleString("en-US") } else { elem.value = info[0][index] } }) } if (info[1] === null) { document.querySelector(".add-contract").classList.remove("d-none") document.querySelector("#futureContractTitle").classList.add("d-none") document.querySelector("#futureContractOptions").classList.add("d-none") document.querySelector("#teamContractButton span").innerText = "Team" document.querySelector("#teamContractButton").dataset.teamid = "-1" } else { document.querySelector(".add-contract").classList.add("d-none") document.querySelector("#futureContractTitle").classList.remove("d-none") document.querySelector("#futureContractOptions").classList.remove("d-none") document.getElementById("futureYear").innerText = "Contract for " + parseInt(info[4] + 1) document.getElementById("futureContract").innerText = getUpdatedName(info[1][6]).toUpperCase() document.querySelector("#teamContractButton").dataset.teamid = info[1][6] document.getElementById("futureContract").className = "team-contract engine-" + team_dict[info[1][6]] document.querySelector("#futureContractOptions").querySelectorAll(".contract-modal-input").forEach(function (elem, index) { if (elem.id === "salaryInputFuture" || elem.id === "signBonusInputFuture" || elem.id === "raceBonusAmtFuture") { elem.value = info[1][index].toLocaleString("en-US") } else if (elem.id === "posInTeamFuture") { setPosInTeamFutureValue(elem, info[1][index], { dispatch: false }); } else { elem.value = info[1][index] } }) } ensureJuniorTeamDropdownBuilt(); const juniorPill = document.querySelector(".contract-category.junior-contract"); if (info[2] !== null) { document.getElementById("contractPills").classList.remove("d-none"); juniorPill?.classList.remove("d-none"); const juniorTeamId = Number(info[2][6]); const juniorButton = document.getElementById("juniorTeamContractButton"); const posInput = document.getElementById("juniorPosInTeam"); if (juniorButton) { juniorTeamIdActive = juniorTeamId; juniorContractDirty = false; juniorButton.dataset.teamid = String(juniorTeamId); const label = juniorButton.querySelector("span"); if (label) label.innerText = (combined_dict[juniorTeamId] || "Select junior team").toUpperCase(); setJuniorPosInputLimits(juniorTeamId); if (posInput) { const pos = Number(info[2][5]); posInput.value = String(pos); } const listDiv = document.querySelector(".junior-team-drivers-list"); if (listDiv) listDiv.innerHTML = "
Loading drivers...
"; const command = new Command("juniorTeamDriversRequest", { teamID: juniorTeamId }); command.execute(); } } else { juniorPill?.classList.remove("d-none"); juniorTeamIdActive = -1; juniorTeamDrivers = []; juniorContractDirty = false; const juniorButton = document.getElementById("juniorTeamContractButton"); if (juniorButton) { juniorButton.dataset.teamid = "-1"; const label = juniorButton.querySelector("span"); if (label) label.innerText = "Select junior team"; } const posInput = document.getElementById("juniorPosInTeam"); if (posInput) { posInput.min = "1"; posInput.max = "3"; posInput.value = "1"; } const listDiv = document.querySelector(".junior-team-drivers-list"); if (listDiv) listDiv.innerHTML = ""; } document.querySelectorAll(".contract-category").forEach(function (el) { el.classList.remove("active"); }); document.querySelector(".contract-category.f1-contract")?.classList.add("active"); document.querySelector("#juniorContractDropdown")?.classList.add("d-none"); document.querySelector("#currentContract")?.classList.remove("d-none"); document.querySelector(".junior-contract-info")?.classList.add("d-none"); document.querySelector("#currentContractOptions")?.classList.remove("d-none"); if (info[1] === null) { document.querySelector(".add-contract")?.classList.remove("d-none"); document.querySelector("#futureContractTitle")?.classList.add("d-none"); document.querySelector("#futureContractOptions")?.classList.add("d-none"); } else { document.querySelector(".add-contract")?.classList.add("d-none"); document.querySelector("#futureContractTitle")?.classList.remove("d-none"); document.querySelector("#futureContractOptions")?.classList.remove("d-none"); } if (driverEditingOfficialDriver || !info[3]) { //if its an official driver or is a staff member, hide junior pill juniorPill?.classList.add("d-none"); } else{ juniorPill?.classList.remove("d-none"); } } function getJuniorMaxCars(teamId) { if (f2_teams.includes(teamId)) return 2; if (f3_teams.includes(teamId)) return 3; if (teamId >= 11 && teamId <= 21) return 2; if (teamId >= 22 && teamId <= 31) return 3; return 2; } function setJuniorPosInputLimits(teamId) { const input = document.getElementById("juniorPosInTeam"); if (!input) return; const maxCars = getJuniorMaxCars(teamId); input.min = "1"; input.max = String(maxCars); const current = Number(input.value || 1); input.value = String(Math.min(maxCars, Math.max(1, current))); } function renderJuniorDriversList() { const listDiv = document.querySelector(".junior-team-drivers-list"); if (!listDiv) return; const maxCars = getJuniorMaxCars(juniorTeamIdActive); const input = document.getElementById("juniorPosInTeam"); const selectedPos = Math.min(maxCars, Math.max(1, Number(input?.value || 1))); const driversByPos = new Map(); (juniorTeamDrivers || []).forEach((d) => { const pos = Number(d?.posInTeam); driversByPos.set(pos, d?.name || "Free driver"); }); listDiv.innerHTML = ""; for (let pos = 1; pos <= maxCars; pos++) { const row = document.createElement("div"); row.className = "junior-driver-row"; const left = document.createElement("div"); left.className = "junior-driver-left"; const car = document.createElement("div"); car.className = "junior-driver-car"; car.innerText = `CAR ${pos}`; const name = document.createElement("div"); name.className = "junior-driver-name"; name.innerText = driversByPos.get(pos) || "Free driver"; left.appendChild(car); left.appendChild(name); const right = document.createElement("div"); right.className = "junior-driver-right"; if (pos === selectedPos) { const tag = document.createElement("div"); tag.className = "junior-replacing-tag"; tag.innerText = "< Replacing"; right.appendChild(tag); } row.appendChild(left); row.appendChild(right); listDiv.appendChild(row); } } function ensureJuniorTeamDropdownBuilt() { const menu = document.getElementById("juniorTeamContractMenu"); if (!menu) return; menu.innerHTML = ""; const juniorIds = Object.keys(combined_dict) .map((k) => Number(k)) .filter((id) => id >= 11 && id <= 31) .sort((a, b) => a - b); juniorIds.forEach((teamId) => { const item = document.createElement("a"); item.className = "redesigned-dropdown-item bold-font"; item.style.cursor = "pointer"; item.dataset.teamid = String(teamId); const logoWrap = document.createElement("div"); logoWrap.className = "team-menu-logo"; const logo = document.createElement("img"); logo.src = logos_disc[teamId] || ""; logo.alt = combined_dict[teamId] || `Team ${teamId}`; logo.className = "team-menu-junior-generic"; logoWrap.appendChild(logo); const nameWrap = document.createElement("div"); nameWrap.className = "team-menu-name"; nameWrap.innerText = (combined_dict[teamId] || `Team ${teamId}`).toUpperCase(); item.appendChild(logoWrap); item.appendChild(nameWrap); menu.appendChild(item); }); menu.querySelectorAll("a").forEach(function (elem) { elem.addEventListener("click", function () { const teamId = Number(elem.dataset.teamid); const button = document.getElementById("juniorTeamContractButton"); const label = button?.querySelector("span"); if (label) label.innerText = elem.querySelector(".team-menu-name")?.innerText || "Select junior team"; if (button) button.dataset.teamid = String(teamId); juniorTeamIdActive = teamId; setJuniorPosInputLimits(teamId); juniorTeamDrivers = []; juniorContractDirty = true; document.querySelector(".junior-contract-info")?.classList.remove("d-none"); const listDiv = document.querySelector(".junior-team-drivers-list"); if (listDiv) listDiv.innerHTML = "
Loading drivers...
"; const command = new Command("juniorTeamDriversRequest", { teamID: teamId }); command.execute(); }); }); const posInput = document.getElementById("juniorPosInTeam"); if (posInput && posInput.dataset.listenerAttached !== "1") { posInput.dataset.listenerAttached = "1"; posInput.addEventListener("input", function () { if (juniorTeamIdActive !== -1) setJuniorPosInputLimits(juniorTeamIdActive); juniorContractDirty = true; renderJuniorDriversList(); }); const wrapper = posInput.closest(".input-and-buttons"); const plusBtn = wrapper?.querySelector(".bi-chevron-down"); const minusBtn = wrapper?.querySelector(".bi-chevron-up"); const wrapStep = (delta) => { const max = Number(posInput.max || 1); const min = Number(posInput.min || 1); const cur = Number(posInput.value || min); const normalized = cur; let next = normalized + delta; if (next > max) next = min; if (next < min) next = max; posInput.value = String(next); posInput.dispatchEvent(new Event("input", { bubbles: true })); }; plusBtn?.addEventListener("click", function () { wrapStep(1); }); minusBtn?.addEventListener("click", function () { wrapStep(-1); }); } } export function loadJuniorTeamDrivers(payload) { const listDiv = document.querySelector(".junior-team-drivers-list"); if (!listDiv) return; const teamId = Number(payload?.teamID); juniorTeamIdActive = teamId; setJuniorPosInputLimits(teamId); juniorTeamDrivers = Array.isArray(payload?.driverNames) ? payload.driverNames : []; renderJuniorDriversList(); } document.querySelectorAll(".contract-category").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelectorAll(".contract-category").forEach(function (el) { el.classList.remove("active") }) elem.classList.add("active"); let category = elem.dataset.category; if (category === "junior") { document.querySelector("#currentContractOptions").classList.add("d-none") // document.querySelector("#futureContractTitle").classList.add("d-none") // document.querySelector("#futureContractOptions").classList.add("d-none") document.querySelector(".add-contract").classList.add("d-none") document.querySelector("#juniorContractDropdown").classList.remove("d-none") document.querySelector("#currentContract").classList.add("d-none") const selectedTeamId = Number(document.getElementById("juniorTeamContractButton")?.dataset.teamid || -1); if (selectedTeamId === -1) { document.querySelector(".junior-contract-info").classList.add("d-none") } else { document.querySelector(".junior-contract-info").classList.remove("d-none") } } else{ document.querySelector("#currentContractOptions").classList.remove("d-none") // document.querySelector("#futureContractTitle").classList.remove("d-none") // document.querySelector("#futureContractOptions").classList.remove("d-none") document.querySelector("#juniorContractDropdown").classList.add("d-none") document.querySelector("#currentContract").classList.remove("d-none") document.querySelector(".add-contract").classList.remove("d-none") document.querySelector(".junior-contract-info").classList.add("d-none") } }) }) /** * Listener for the team menu buttons */ document.querySelector("#teamContractMenu").querySelectorAll("a").forEach(function (elem) { elem.addEventListener("click", function () { document.querySelector("#teamContractButton span").innerText = elem.querySelector(".team-menu-name").innerText; document.querySelector("#teamContractButton").dataset.teamid = elem.dataset.teamid; document.querySelector(".add-contract").classList.add("enabled") }) }) function formatNumber(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } function formatPosInTeamFutureLabel(pos) { const n = Math.trunc(Number(pos)); if (n === 1) return "Car 1"; if (n === 2) return "Car 2"; return `Reserve ${n}`; } function parsePosInTeamFutureValue(raw) { if (raw === null || raw === undefined) return NaN; const str = String(raw).trim(); if (!str) return NaN; const match = str.match(/(\d+)/); if (!match) return NaN; return Number(match[1]); } function getPosInTeamFutureValue(input) { if (!input) return NaN; const parsed = parsePosInTeamFutureValue(input.value); const fromDataset = Number(input.dataset.posValue); return parsed || fromDataset || 1; } function setPosInTeamFutureValue(input, pos, opts = {}) { if (!input) return; const min = input.min !== "" ? Number(input.min) : -Infinity; const max = input.max !== "" ? Number(input.max) : Infinity; let next = Math.trunc(Number(pos)); next = Math.max(min, next); next = Math.min(max, next); input.dataset.posValue = String(next); input.value = formatPosInTeamFutureLabel(next); if (opts.dispatch !== false) { input.dispatchEvent(new Event("input", { bubbles: true })); } } function attachHoldPosInTeamFuture(btn, input, delta) { if (!btn || !input) return; if (btn.dataset.holdAttached === "1") return; btn.dataset.holdAttached = "1"; let intervalId = null; let timeoutId = null; const stepOnce = () => { const current = getPosInTeamFutureValue(input); setPosInTeamFutureValue(input, current + delta); }; const clearTimers = () => { if (timeoutId !== null) clearTimeout(timeoutId); if (intervalId !== null) clearInterval(intervalId); timeoutId = null; intervalId = null; }; btn.addEventListener("pointerdown", (e) => { e.preventDefault(); clearTimers(); stepOnce(); timeoutId = setTimeout(() => { intervalId = setInterval(stepOnce, 75); }, 350); }); ["pointerup", "pointercancel", "pointerleave", "lostpointercapture"].forEach((evt) => { btn.addEventListener(evt, clearTimers); }); } function setupPosInTeamFutureControls(input, plusBtn, minusBtn) { if (!input || !plusBtn || !minusBtn) return; if (input.dataset.posSetup === "1") return; input.dataset.posSetup = "1"; input.type = "text"; input.placeholder = "ex: Car 1"; const initial = getPosInTeamFutureValue(input); setPosInTeamFutureValue(input, initial, { dispatch: false }); input.addEventListener("change", () => { setPosInTeamFutureValue(input, getPosInTeamFutureValue(input), { dispatch: false }); }); attachHoldPosInTeamFuture(plusBtn, input, +1); attachHoldPosInTeamFuture(minusBtn, input, -1); } document.querySelector(".add-contract .button-with-icon").addEventListener("click", function () { document.getElementById("yearInput").value = document.getElementById("yearInput").dataset.maxYear document.querySelector("#futureYear").innerText = "Next year's contract" document.querySelector("#futureContract").className = "team-contract engine-" + team_dict[document.querySelector("#teamContractButton").dataset.teamid] document.querySelector("#futureContract").innerText = document.querySelector("#teamContractButton span").innerText document.querySelector(".add-contract").classList.add("d-none") document.querySelector("#futureContractTitle").classList.remove("d-none") document.querySelector("#futureContractOptions").classList.remove("d-none") if (document.querySelector("#salaryInput").value !== "") { document.querySelector("#salaryInputFuture").value = formatNumber((parseFloat(document.querySelector("#salaryInput").value.replace(/[$,]/g, '')) * 1.3).toFixed(0)); document.querySelector("#signBonusInputFuture").value = formatNumber((parseFloat(document.querySelector("#signBonusInput").value.replace(/[$,]/g, '')) * 1.15).toFixed(0)); document.querySelector("#raceBonusAmtFuture").value = formatNumber((parseFloat(document.querySelector("#raceBonusAmt").value.replace(/[$,]/g, '')) * 1.15).toFixed(0)); document.querySelector("#raceBonusPosFuture").value = parseInt(document.querySelector("#raceBonusPos").value) document.querySelector("#yearInputFuture").value = parseInt(document.querySelector("#yearInput").value) + 2 } else { document.querySelector("#salaryInputFuture").value = "1,000,000" document.querySelector("#signBonusInputFuture").value = "100,000" document.querySelector("#raceBonusAmtFuture").value = "0" document.querySelector("#raceBonusPosFuture").value = "1" document.querySelector("#yearInputFuture").value = parseInt(currentSeason) + 1 } setPosInTeamFutureValue(document.querySelector("#posInTeamFuture"), 1); }) document.querySelector(".break-contract").addEventListener("click", function () { document.querySelector(".add-contract").classList.remove("d-none") document.querySelector("#futureContractTitle").classList.add("d-none") document.querySelector("#futureContractOptions").classList.add("d-none") document.querySelector("#teamContractButton span").innerText = "Team" document.querySelector("#teamContractButton").dataset.teamid = "-1" document.querySelector(".add-contract").classList.remove("enabled") }) function attachHoldWithAttrClamp(btn, input, step, opts = {}) { if (!btn || !input) return; if (btn.dataset.holdAttached === "1") return; btn.dataset.holdAttached = "1"; const format = typeof opts.format === "function" ? opts.format : undefined; attachHold(btn, input, step, { min: -Infinity, max: Infinity, format, onChange: (val) => { if (typeof val !== "number") return; const min = input.min !== "" ? Number(input.min) : -Infinity; const max = input.max !== "" ? Number(input.max) : Infinity; let clamped = val; clamped = Math.max(min, clamped); clamped = Math.min(max, clamped); if (clamped === val) return; input.value = String(format ? format(clamped) : clamped); input.dispatchEvent(new Event("input", { bubbles: true })); }, }); } function setupContractModalButtons() { const moneyFormat = (val) => Number(val).toLocaleString("en-US"); const moneyInputs = new Set([ "salaryInput", "signBonusInput", "raceBonusAmt", "salaryInputFuture", "signBonusInputFuture", "raceBonusAmtFuture", ]); document.querySelectorAll("#contractModal .contract-options .input-and-buttons").forEach((wrapper) => { const input = wrapper.querySelector("input"); const plusBtn = wrapper.querySelector(".bi-plus"); const minusBtn = wrapper.querySelector(".bi-dash"); if (!input || !plusBtn || !minusBtn) return; if (input.id === "juniorPosInTeam") return; if (input.id === "posInTeamFuture") { setupPosInTeamFutureControls(input, plusBtn, minusBtn); return; } const isMoney = moneyInputs.has(input.id); const isSalary = input.id === "salaryInput" || input.id === "salaryInputFuture"; const isRacePos = input.id === "raceBonusPos" || input.id === "raceBonusPosFuture"; const baseStep = isSalary ? 100000 : isMoney ? 10000 : 1; const plusStep = isRacePos ? -baseStep : +baseStep; const minusStep = isRacePos ? +baseStep : -baseStep; attachHoldWithAttrClamp(plusBtn, input, plusStep, { format: isMoney ? moneyFormat : undefined }); attachHoldWithAttrClamp(minusBtn, input, minusStep, { format: isMoney ? moneyFormat : undefined }); }); } setupContractModalButtons(); /** * Sends the message that requests the details from the driver * @param {div} elem div from the driver its requesting its details */ function fetchContracts(elem) { driverEditingID = elem.dataset.driverid driverEditingName = make_name_prettier(elem.innerText) const command = new Command("driverRequest", { driverID: driverEditingID }); command.execute(); } /** * Manages the state of the categorias * @param {...string} divs the state of each div */ function manageDrivers(...divs) { divsArray.forEach(function (div, index) { if (divs[index] === "show") { div.className = "main-columns-drag-section" } else { div.className = "main-columns-drag-section d-none" } }) } /** * Event listener for the confirm button from the modal */ document.getElementById("confirmButton").addEventListener('click', function () { const juniorActive = document.querySelector(".contract-category.junior-contract")?.classList.contains("active"); if (juniorActive) { if (juniorContractDirty) { juniorContractDirty = false; } let data = { driverID: driverEditingID, driver: driverEditingName, team: combined_dict[juniorTeamIdActive] || "", teamID: juniorTeamIdActive, posInTeam: document.getElementById("juniorPosInTeam").value } const command = new Command("juniorTransfer", data); command.execute(); modalType = ""; setTimeout(clearModal, 500); return; } if (modalType === "hire") { if (((f2_teams.includes(originalTeamId) | f3_teams.includes(originalTeamId)) && !destinationParent.classList.contains("affiliates-space")) | originalParent.className === "driver-space" | originalParent.classList.contains("affiliates-space") | originalParent.className === "staff-space") { signDriver("fireandhire") } signDriver("regular") modalType = ""; } else if (modalType === "edit") { editContract() modalType = ""; } setTimeout(clearModal, 500); sortList("free-drivers") sortList("free-staff") }) /** * Clears the modal's inputs */ function clearModal() { document.querySelectorAll(".contract-modal-input").forEach(function (elem) { if (elem.id === "posInTeamFuture") { delete elem.dataset.posValue; } elem.value = "" }) } /** * Sends the message to the backend to edit the contract */ function editContract() { let values = [] document.querySelector("#currentContractOptions").querySelectorAll(".contract-modal-input").forEach(function (elem) { if (elem.id === "salaryInput" || elem.id === "signBonusInput" || elem.id === "raceBonusAmt") { values.push(elem.value.replace(/[$,]/g, "")) } else { values.push(elem.value) } }) let futureValues = [] document.querySelector("#futureContractOptions").querySelectorAll(".contract-modal-input").forEach(function (elem) { if (elem.id === "salaryInputFuture" || elem.id === "signBonusInputFuture" || elem.id === "raceBonusAmtFuture") { futureValues.push(elem.value.replace(/[$,]/g, "")) } else if (elem.id === "posInTeamFuture") { futureValues.push(String(getPosInTeamFutureValue(elem))) } else { futureValues.push(elem.value) } }) let future_team = document.querySelector("#teamContractButton").dataset.teamid let data = { driverID: driverEditingID, salary: values[0], year: values[1], signBonus: values[2], raceBonus: values[3], raceBonusPos: values[4], driver: driverEditingName, futureTeam: future_team, futureSalary: futureValues[0], futureYear: futureValues[1], futureSignBonus: futureValues[2], futureRaceBonus: futureValues[3], futureRaceBonusPos: futureValues[4], futurePosition: futureValues[5] } const command = new Command("editContract", data); command.execute(); console.log("FUTURE TEAM: ", future_team) let teamInfo = { teamId: future_team, posInTeam: futureValues[5] } if (future_team !== "-1") { let driverDiv = document.querySelector('.free-driver[data-driverid="' + driverEditingID + '"]') add_future_team_noti(driverDiv, teamInfo) driverDiv.dataset.futureteam = future_team } else { let driverDiv = document.querySelector('.free-driver[data-driverid="' + driverEditingID + '"]') driverDiv.querySelector(".future-contract-noti").remove() driverDiv.dataset.futureteam = -1 } } /** * Changes the positions of 2 drivers involved in a swap */ function manage_swap() { let parent1 = driver1.parentNode; let parent2 = driver2.parentNode; parent1.removeChild(driver1); parent2.removeChild(driver2); parent1.appendChild(driver2); parent2.appendChild(driver1); } /** * Sends the necessary messages to hire a driver * @param {string} type type of the hiring of the driver, depending if he needs to be fired before or not */ function signDriver(type) { let driverName = make_name_prettier(draggable.innerText) if (type === "fireandhire") { let data = { driverID: draggable.dataset.driverid, driver: driverName, team: getUpdatedName(inverted_dict[teamOrigin.dataset.team]), teamID: originalTeamId } if (!data["team"]) { if (f2_teams.includes(originalTeamId)) { data["team"] = "F2" } else if (f3_teams.includes(originalTeamId)) { data["team"] = "F3" } } const command = new Command("fireDriver", data); command.execute(); } if (type === "regular") { let salaryData = document.getElementById("salaryInput").value.replace(/[$,]/g, ""); let yearData = document.getElementById("yearInput").value; let signBonusData = document.getElementById("signBonusInput").value.replace(/[$,]/g, ""); let raceBonusData; let raceBonusPosData; if (signBonusData === "") signBonusData = "0" if (raceBonusAmt.value === "") raceBonusData = "0"; else raceBonusData = raceBonusAmt.value.replace(/[$,]/g, ""); if (raceBonusPos.value === "") raceBonusPosData = "10"; else raceBonusPosData = raceBonusPos.value; let data = { driverID: draggable.dataset.driverid, teamID: inverted_dict[teamDestiniy], position: posInTeam, salary: salaryData, signBonus: signBonusData, raceBonus: raceBonusData, raceBonusPos: raceBonusPosData, year: yearData, driver: driverName, team: name_dict[teamDestiniy] } destinationParent.appendChild(draggable); const command = new Command("hireDriver", data); command.execute(); } else if (type === "autocontract") { let dataAuto = { driverID: draggable.dataset.driverid, teamID: inverted_dict[teamDestiniy], position: posInTeam, driver: driverName, team: name_dict[teamDestiniy] } destinationParent.appendChild(draggable); const command = new Command("autoContract", dataAuto); command.execute(); } } /** * Event listener for the cancel button on the modal */ document.getElementById("cancelButton").addEventListener('click', function () { document.querySelector(".add-contract").classList.remove("enabled") if (modalType === "hire") { originalParent.appendChild(draggable); draggable.dataset.teamid = inverted_dict[teamOrigin.dataset.team] updateColor(draggable) } setTimeout(clearModal, 500); }) document.querySelector("#nameFilterTransfer").addEventListener("input", (e) => { const val = e.target.value; clearIcon.classList.toggle("d-none", val === ""); clearTimeout(t); t = setTimeout(() => { const q = val.trim().toLowerCase(); if (!q) { for (const {el} of freeDriverItems) el.classList.remove("d-none"); return; } for (const {el, name} of freeDriverItems) { el.classList.toggle("d-none", !name.includes(q)); } }, 150); }); document.querySelector("#filterIconTransfers").addEventListener("click", function () { document.querySelector(".category-filters").classList.toggle("show") document.querySelector(".filter-container").classList.toggle("focused") if (document.querySelector(".filter-container").classList.contains("focused")) { document.querySelector("#filterIconTransfers").className = "bi bi-filter-circle-fill filter-icon" } else { document.querySelector("#filterIconTransfers").className = "bi bi-filter-circle filter-icon" } }) document.getElementById("driver_transfers").querySelectorAll(".new-pills-filters").forEach(function (elem) { elem.addEventListener("click", function (event) { let isActive = elem.classList.contains('active'); document.getElementById("driver_transfers").querySelectorAll('.new-pills-filters').forEach(function (el) { el.classList.remove('active'); }); if (!isActive) { elem.classList.add('active'); } }) }) document.querySelector("#F2filterTransfers").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { elem.classList.remove("d-none") }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) <= 21 && parseInt(elem.dataset.teamid) > 10) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) <= 21 && parseInt(elem.dataset.teamid) > 10) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector("#F3filterTransfers").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { elem.classList.remove("d-none") }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) <= 31 && parseInt(elem.dataset.teamid) > 21) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) <= 31 && parseInt(elem.dataset.teamid) > 21) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) document.querySelector("#freefilterTransfers").addEventListener("click", function (event) { if (!event.target.classList.contains("active")) { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { elem.classList.remove("d-none") }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { elem.classList.remove("d-none") }) } else { let driverElements = document.getElementById("free-drivers").querySelectorAll(".free-driver") driverElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) == 0) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) let staffElements = document.getElementById("free-staff").querySelectorAll(".free-driver") staffElements.forEach(function (elem) { if (parseInt(elem.dataset.teamid) == 0) { elem.classList.remove("d-none") } else { elem.classList.add("d-none") } }) } }) function setLineupsButtonState(isOpen) { if (!lineupsViewButton) return; const label = lineupsViewButton.querySelector("span"); const icon = lineupsViewButton.querySelector("i"); if (label) { label.textContent = isOpen ? "Transfers" : "Line ups"; } if (icon) { icon.className = isOpen ? "bi bi-arrow-return-left" : "bi bi-diagram-3"; } } function getLineupsDisplaySeason() { const baseSeason = Number(lineupsData?.season) || Number(currentSeason) || 0; if (lineupsSeasonType === "next") return baseSeason + 1; return baseSeason; } function setLineupsSeasonPillActive(seasonType) { lineupsSeasonType = seasonType; if (lineupsCurrentPill) { lineupsCurrentPill.classList.toggle("active", seasonType === "current"); } if (lineupsNextPill) { lineupsNextPill.classList.toggle("active", seasonType === "next"); } } function getLineupsTeamIds(payload) { const availableIds = Array.isArray(payload?.teamIds) ? payload.teamIds.map(id => Number(id)) : Object.keys(payload?.teams || {}).map(id => Number(id)); const standardOrder = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 32]; const standingsRows = Array.isArray(payload?.previousSeasonStandings) ? payload.previousSeasonStandings : []; const standingsOrder = standingsRows .map((row) => Number(Array.isArray(row) ? row[0] : (row?.teamId ?? row?.TeamID))) .filter((teamId) => !Number.isNaN(teamId) && availableIds.includes(teamId)); const uniqueStandingsOrder = [...new Set(standingsOrder)]; const fallbackOrder = standardOrder.filter((id) => availableIds.includes(id) && !uniqueStandingsOrder.includes(id)); return [...uniqueStandingsOrder, ...fallbackOrder]; } function buildFallbackLogo(teamId) { const logo = document.createElement("img"); logo.src = logos_disc[teamId] || "../assets/images/logos/placeholder.png"; logo.className = "standardlogo"; return logo; } function getTeamTemplateById(teamId) { const staffSection = document.querySelector(`.staff-section[data-teamid="${teamId}"]`); return staffSection ? staffSection.closest(".team-template") : null; } function createTeamLogoNode(teamId) { const logoContainer = document.createElement("div"); logoContainer.classList.add("lineups-team-logo"); const teamTemplate = getTeamTemplateById(teamId); const sourceLogo = teamTemplate?.querySelector(".new-logo-and-name")?.children?.[0]; if (sourceLogo) { logoContainer.appendChild(sourceLogo.cloneNode(true)); } else { logoContainer.appendChild(buildFallbackLogo(teamId)); } return logoContainer; } function getTeamNameForLineup(teamId, teamInfo) { const teamTemplate = getTeamTemplateById(teamId); const templateName = teamTemplate?.querySelector(".team-name")?.textContent?.trim(); if (templateName && templateName.length > 0) return templateName; if (teamInfo?.name) return teamInfo.name; return combined_dict[teamId] || "Unknown Team"; } function normalizeDriverNumber(value) { const parsed = Number(value); return !Number.isNaN(parsed) && parsed > 0 ? Math.trunc(parsed) : null; } function getDriverSeatPriority(driver, seasonType) { let priority = 0; const contractType = Number(driver?.contractType); if (seasonType === "next" && contractType === 3) priority += 100; if (contractType === 0) priority += 50; if (driver?.isForNextSeason) priority += 20; if (normalizeDriverNumber(driver?.driverNumber) != null) priority += 1; return priority; } function getLineupDriversBySeat(teamInfo, seasonType) { const sourceDrivers = seasonType === "next" ? (teamInfo?.driversNextSeason || []) : (teamInfo?.driversThisSeason || []); const slots = [null, null]; const priorities = [-Infinity, -Infinity]; const overflow = []; sourceDrivers.forEach((driver) => { const seat = Number(driver?.posInTeam); if (seat === 1 || seat === 2) { const idx = seat - 1; const nextPriority = getDriverSeatPriority(driver, seasonType); if (slots[idx] == null || nextPriority > priorities[idx]) { slots[idx] = driver; priorities[idx] = nextPriority; } return; } overflow.push(driver); }); for (let idx = 0; idx < 2; idx++) { if (!slots[idx] && overflow.length > 0) { slots[idx] = overflow.shift(); } } return slots.map((driver, idx) => driver || { name: "TBD", posInTeam: idx + 1 }); } function createLineupDriverRows(teamInfo, seasonType) { const rows = document.createElement("div"); rows.classList.add("lineups-team-drivers"); const drivers = getLineupDriversBySeat(teamInfo, seasonType); drivers.forEach((driver) => { const line = document.createElement("div"); line.classList.add("lineups-team-driver"); if (seasonType === "next" && driver?.isForNextSeason) { line.classList.add("next-contract"); } const driverNumber = normalizeDriverNumber(driver?.driverNumber); if (driverNumber != null) { const numberMark = document.createElement("span"); numberMark.classList.add("lineups-driver-number"); numberMark.textContent = String(driverNumber); line.appendChild(numberMark); } const rawName = (driver?.name || "TBD").trim(); if (rawName === "TBD") { const last = document.createElement("span"); last.classList.add("lineups-driver-last", "bold-font", "to-be-decided"); last.textContent = "TBD"; line.appendChild(last); rows.appendChild(line); return; } const parts = rawName.split(/\s+/); const surname = parts.pop() || ""; const firstNames = parts.join(" "); if (firstNames) { const first = document.createElement("span"); first.classList.add("lineups-driver-first"); first.textContent = firstNames; line.appendChild(first); } const last = document.createElement("span"); last.classList.add("lineups-driver-last", "bold-font"); last.textContent = surname.toUpperCase(); line.appendChild(last); rows.appendChild(line); }); return rows; } function buildLineupCard(teamId, teamInfo, seasonType) { const card = document.createElement("div"); card.classList.add("lineups-team-card"); if (teamId === 32) { card.classList.add("lineups-custom-team"); } const accentByTeamId = { 1: "var(--ferrari-primary)", 2: "var(--mclaren-primary)", 3: "var(--redbull-primary)", 4: "var(--mercedes-primary)", 5: "var(--alpine-primary)", 6: "var(--williams-primary)", 7: "var(--haas-primary)", 8: "var(--alphatauri-primary)", 9: "var(--alfa-primary)", 10: "var(--aston-primary)", 32: "var(--custom-team-primary)" }; card.style.setProperty("--lineup-accent", accentByTeamId[teamId] || "var(--new-primary)"); const header = document.createElement("div"); header.classList.add("lineups-team-header"); const teamName = document.createElement("div"); teamName.classList.add("lineups-team-name", "bold-font"); const lineupTeamName = getTeamNameForLineup(teamId, teamInfo); teamName.textContent = lineupTeamName; header.appendChild(teamName); header.appendChild(createTeamLogoNode(teamId)); card.appendChild(header); card.appendChild(createLineupDriverRows(teamInfo, seasonType)); return card; } function computeLineupPositions(teamIds, width, height) { const positions = new Map(); const hasCenterBottomTeam = teamIds.length % 2 === 1; const centerBottomTeamId = hasCenterBottomTeam ? teamIds[teamIds.length - 1] : null; const pairedTeams = hasCenterBottomTeam ? teamIds.slice(0, -1) : teamIds.slice(); // Alternate by standings rank: 1st left, 2nd right, 3rd left, 4th right, ... const leftTeams = pairedTeams.filter((_, idx) => idx % 2 === 0); const rightTeams = pairedTeams.filter((_, idx) => idx % 2 === 1); const topY = height * 0.06; const bottomY = hasCenterBottomTeam ? (height * 0.80) : (height * 0.90); const buildYPositions = (count) => { if (count <= 0) return []; if (count === 1) return [height * 0.5]; const step = (bottomY - topY) / (count - 1); return Array.from({ length: count }, (_, i) => topY + (i * step)); }; const placeColumn = (teamsInColumn, side) => { const yPositions = buildYPositions(teamsInColumn.length); const baseX = side === "left" ? (width * 0.23) : (width * 0.77); const outwardShift = width * 0.23; const cardHalfWidth = width <= 900 ? 98 : (width <= 1200 ? 121 : 132); const denom = Math.max(1, teamsInColumn.length - 1); const getArcStrength = (idx) => { // Hand-tuned 5-row profile: tighter top pair, strong middle opening, // and configurable bottom pair spacing when there is a bottom-center team. if (teamsInColumn.length === 5) { if (hasCenterBottomTeam) { // Wider bottom spread to leave cleaner space for team 11 in the center. return [-0.7, -0.1, 0.3, 0.1, -0.3][idx]; } // Standard saves: bottom pair spacing mirrors top pair spacing. return [-0.6, -0.1, 0.2, -0.1, -0.6][idx]; } const t = idx / denom; const arcBase = 1 - (Math.abs(t - 0.5) * 2); return Math.max(0, arcBase); }; teamsInColumn.forEach((teamId, idx) => { const arcStrength = getArcStrength(idx); const x = side === "left" ? (baseX - outwardShift * arcStrength) : (baseX + outwardShift * arcStrength); const clampedX = side === "left" ? Math.max(cardHalfWidth + 12, x) : Math.min(width - cardHalfWidth - 12, x); positions.set(teamId, { x: clampedX, y: yPositions[idx] }); }); }; placeColumn(leftTeams, "left"); placeColumn(rightTeams, "right"); if (centerBottomTeamId != null) { positions.set(centerBottomTeamId, { x: width * 0.5, y: height * 0.91 }); } return positions; } function updateLineupPillLabels(season) { if (lineupsCurrentPill) { const label = lineupsCurrentPill.querySelector(".lineups-pill-label"); if (label) label.textContent = `${season}`; } if (lineupsNextPill) { const label = lineupsNextPill.querySelector(".lineups-pill-label"); if (label) label.textContent = `${season + 1}`; } } function renderLineupsCircle() { if (!lineupsCircle || !lineupsData) return; lineupsCircle.innerHTML = ""; const teamIds = getLineupsTeamIds(lineupsData); if (teamIds.length === 0) { return; } const rect = lineupsCircle.getBoundingClientRect(); if (rect.width <= 0 || rect.height <= 0) return; const positions = computeLineupPositions(teamIds, rect.width, rect.height); const teams = lineupsData.teams || {}; const centerTitle = document.createElement("div"); centerTitle.classList.add("lineups-center-title"); const displaySeason = getLineupsDisplaySeason(); const yearText = displaySeason > 0 ? `${displaySeason}` : "F1"; centerTitle.innerHTML = ` ${yearText} DRIVER LINE UPS `; lineupsCircle.appendChild(centerTitle); teamIds.forEach((teamId) => { const teamInfo = teams[teamId] || teams[String(teamId)] || {}; const card = buildLineupCard(teamId, teamInfo, lineupsSeasonType); const pos = positions.get(teamId); if (!pos) return; card.style.left = `${pos.x}px`; card.style.top = `${pos.y}px`; lineupsCircle.appendChild(card); }); } function renderLineupsAfterLayout() { requestAnimationFrame(() => { renderLineupsCircle(); }); } function refreshLineupsCircleAfterTeamReplace() { if (!lineupsView || lineupsView.classList.contains("d-none")) return; if (!lineupsCircle || !lineupsData) return; renderLineupsAfterLayout(); } document.addEventListener("teamsReplaced", function () { refreshLineupsCircleAfterTeamReplace(); }); async function fetchLineupsData() { const command = new Command("lineupsRequest", {}); const response = await command.promiseExecute(); lineupsData = response.content || null; return lineupsData; } function closeLineupsView() { if (!lineupsView || !transfersMainLayout) return; lineupsView.classList.add("d-none"); transfersMainLayout.classList.remove("d-none"); lineupsSeasonPillsTop?.classList.add("d-none"); setLineupsButtonState(false); } async function openLineupsView() { if (!lineupsView || !transfersMainLayout) return; setLineupsButtonState(true); transfersMainLayout.classList.add("d-none"); lineupsView.classList.remove("d-none"); lineupsSeasonPillsTop?.classList.remove("d-none"); try { const payload = await fetchLineupsData(); const season = Number(payload?.season) || Number(currentSeason) || 0; if (season > 0) { updateLineupPillLabels(season); } setLineupsSeasonPillActive("current"); renderLineupsAfterLayout(); } catch (err) { console.error("Error fetching line ups:", err); } } lineupsViewButton?.addEventListener("click", async function () { const isClosed = lineupsView?.classList.contains("d-none"); if (isClosed) { await openLineupsView(); } else { closeLineupsView(); } }); [lineupsCurrentPill, lineupsNextPill].forEach((pill) => { pill?.addEventListener("click", function () { const seasonType = pill.dataset.seasonType; if (!seasonType) return; setLineupsSeasonPillActive(seasonType); renderLineupsCircle(); }); }); window.addEventListener("resize", function () { if (lineupsView && !lineupsView.classList.contains("d-none")) { renderLineupsAfterLayout(); } }); function hire_modal_standars() { document.querySelector(".add-contract").classList.add("d-none") document.querySelector("#futureContractTitle").classList.add("d-none") document.querySelector("#futureContractOptions").classList.add("d-none") document.querySelector("#juniorContractDropdown").classList.add("d-none") document.querySelector("#contractPills").classList.add("d-none") document.getElementById("currentContract").innerText = getUpdatedName(inverted_dict[teamDestiniy]).toUpperCase() document.getElementById("currentContract").className = "team-contract engine-" + team_dict[inverted_dict[teamDestiniy]] } /** * Manages the interaction to drag drivers */ interact('.free-driver').draggable({ inertia: true, listeners: { start(event) { originalParent = event.target.parentNode; if (originalParent.className != "main-columns-drag-section") { if (originalParent.classList.contains("affiliates-space")) { teamOrigin = originalParent.parentNode.parentNode } else { teamOrigin = originalParent.parentNode } } else { teamOrigin = originalParent } draggable = event.target; let target = event.target; let position = target.getBoundingClientRect(); let width = target.getBoundingClientRect().width target.style.width = width + "px"; target.style.position = "fixed"; target.style.top = position.top + "px"; target.style.left = position.left + "px"; // Añadir esta línea para manejar la posición izquierda }, move(event) { const target = event.target; const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx; const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy; target.style.transform = `translate(${x}px, ${y}px)`; target.style.opacity = 1; target.style.zIndex = 10; target.setAttribute('data-x', x); target.setAttribute('data-y', y); }, end(event) { let target = event.target; target.style.position = "relative"; target.style.top = "auto"; target.style.left = "auto"; // Resetear la posición izquierda target.style.width = "auto"; target.style.transform = 'none'; target.style.zIndex = 1; target.setAttribute('data-x', 0); target.setAttribute('data-y', 0); //is driver if (event.target.parentNode.classList.contains("driver-space") | event.target.parentNode.classList.contains("affiliates-space") | event.target.parentNode.id === "free-drivers") { let freeDrivers = document.getElementById('free-drivers'); let freeRect = freeDrivers.getBoundingClientRect(); let driverSpaceElements = document.querySelectorAll('.driver-space'); driverSpaceElements.forEach(function (element) { let rect = element.getBoundingClientRect(); if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) { if (element.classList.contains("affiliates-space") && game_version === 2024) { posInTeam = 3 + element.childElementCount teamDestiniy = element.parentNode.parentNode.dataset.team destinationParent = element; element.appendChild(target) originalTeamId = parseInt(target.dataset.teamid) target.dataset.teamid = inverted_dict[teamDestiniy] updateColor(target) document.getElementById("contractModalTitle").innerText = target.innerText + "'s contract with " + name_dict[teamDestiniy]; if (autoContractToggle.checked) { if ((game_version === 2024) && (originalParent.className === "driver-space" | originalParent.classList.contains("affiliates-space"))) { signDriver("fireandhire") } signDriver("autocontract") } else { modalType = "hire" hire_modal_standars() manage_modal_driver_staff("driver") myModal.show() } if (target.querySelector(".custom-icon") === null) { addIcon(target) } } else { if (element.childElementCount < 1) { posInTeam = element.id.charAt(2) teamDestiniy = element.parentNode.dataset.team destinationParent = element; element.appendChild(target); originalTeamId = parseInt(target.dataset.teamid) target.dataset.teamid = inverted_dict[teamDestiniy] updateColor(target) document.getElementById("contractModalTitle").innerText = target.innerText + "'s contract with " + name_dict[teamDestiniy]; if (autoContractToggle.checked) { if ((game_version === 2023 && (f2_teams.includes(originalTeamId) | f3_teams.includes(originalTeamId) | originalParent.className === "driver-space" | originalParent.classList.contains("affiliates-space"))) || (game_version === 2024) && (f2_teams.includes(originalTeamId) | f3_teams.includes(originalTeamId) | originalParent.className === "driver-space" | originalParent.classList.contains("affiliates-space"))) { signDriver("fireandhire") } signDriver("autocontract") } else { modalType = "hire" hire_modal_standars() manage_modal_driver_staff("driver") myModal.show() } if (target.querySelector(".custom-icon") === null) { addIcon(target) } } else if (element.childElementCount == 1) { if (originalParent.classList.contains("driver-space")) { driver1 = target; driver2 = element.firstChild; let team1 = driver1.parentNode.parentNode let team2 = driver2.parentNode.parentNode driver1.dataset.teamid = inverted_dict[team2.dataset.team] updateColor(driver1) driver2.dataset.teamid = inverted_dict[team1.dataset.team] updateColor(driver2) if (driver1 !== driver2) { let data = { driver1ID: target.dataset.driverid, driver2ID: element.firstChild.dataset.driverid, driver1: make_name_prettier(target.innerText), driver2: make_name_prettier(element.firstChild.innerText), } const command = new Command("swapDrivers", data); command.execute(); manage_swap() } } } } } }); if (event.clientX >= freeRect.left && event.clientX <= freeRect.right && event.clientY >= freeRect.top && event.clientY <= freeRect.bottom) { if (target.querySelector(".custom-icon") !== null) { draggable.removeChild(draggable.querySelector(".custom-icon")) } if (originalParent.id !== "free-drivers") { originalParent.removeChild(draggable); originalTeamId = parseInt(target.dataset.teamid) draggable.dataset.teamid = 0 updateColor(draggable) freeDrivers.appendChild(target); let data = { driverID: draggable.dataset.driverid, driver: make_name_prettier(draggable.innerText), team: getUpdatedName(inverted_dict[teamOrigin.dataset.team]), teamID: originalTeamId } if (!data["team"]) { if (f2_teams.includes(originalTeamId)) { data["team"] = "F2" } else if (f3_teams.includes(originalTeamId)) { data["team"] = "F3" } } const command = new Command("fireDriver", data); command.execute(); } } } //is staff else if (event.target.parentNode.classList.contains("staff-space") | event.target.parentNode.id === "free-staff") { let tfreeStaff = document.getElementById('free-staff'); let staffRect = tfreeStaff.getBoundingClientRect(); let staffSpaceElements = document.querySelectorAll('.staff-space'); staffSpaceElements.forEach(function (element) { let rect = element.getBoundingClientRect(); if (event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom) { if (element.dataset.type === event.target.dataset.type) { if (element.childElementCount < 1) { posInTeam = element.dataset.pos teamDestiniy = element.parentNode.dataset.team destinationParent = element; element.appendChild(target); originalTeamId = parseInt(target.dataset.teamid) target.dataset.teamid = inverted_dict[teamDestiniy] updateColor(target) document.getElementById("contractModalTitle").innerText = target.innerText + "'s contract with " + name_dict[teamDestiniy]; if (autoContractToggle.checked) { if ((game_version === 2023 && (f2_teams.includes(originalTeamId) | f3_teams.includes(originalTeamId) | originalParent.className === "staff-space")) || (game_version === 2024) && (f2_teams.includes(originalTeamId) | f3_teams.includes(originalTeamId) | originalParent.className === "staff-space")) { signDriver("fireandhire") } signDriver("autocontract") } else { modalType = "hire" hire_modal_standars() if (event.target.dataset.type === "race-engineer") { manage_modal_driver_staff("race-engineer") } else { manage_modal_driver_staff("staff") } myModal.show() } if (target.querySelector(".custom-icon") === null) { addIcon(target) } } else if (element.childElementCount == 1) { if (originalParent.classList.contains("staff-space")) { driver1 = target; driver2 = element.firstChild; let team1 = driver1.parentNode.parentNode let team2 = driver2.parentNode.parentNode driver1.dataset.teamid = inverted_dict[team2.dataset.team] updateColor(driver1) driver2.dataset.teamid = inverted_dict[team1.dataset.team] updateColor(driver2) if (driver1 !== driver2) { let data = { driver1ID: target.dataset.driverid, driver2ID: element.firstChild.dataset.driverid, driver1: target.innerText, driver2: element.firstChild.innerText, } const command = new Command("swapDrivers", data); command.execute(); manage_swap() } } } } else { update_notifications("You can't change staff from different positions", "lighterror") } } }); if (event.clientX >= staffRect.left && event.clientX <= staffRect.right && event.clientY >= staffRect.top && event.clientY <= staffRect.bottom) { if (target.querySelector(".custom-icon") !== null) { draggable.removeChild(draggable.querySelector(".custom-icon")) } if (originalParent.id !== "free-staff") { originalParent.removeChild(draggable); originalTeamId = parseInt(target.dataset.teamid) draggable.dataset.teamid = 0 updateColor(draggable) tfreeStaff.appendChild(target); let data = { driverID: draggable.dataset.driverid, driver: make_name_prettier(draggable.innerText), team: getUpdatedName(inverted_dict[teamOrigin.dataset.team]), teamID: originalTeamId } if (!data["team"]) { if (f2_teams.includes(originalTeamId)) { data["team"] = "F2" } else if (f3_teams.includes(originalTeamId)) { data["team"] = "F3" } } const command = new Command("fireDriver", data); command.execute(); } } } } } }); ================================================ FILE: src/styles.css ================================================ @font-face { font-family: 'Formula1'; src: url("../assets/fonts/Formula1-Regular.ttf") format('truetype'); } @font-face { font-family: 'Formula1Bold'; src: url("../assets/fonts/Formula1-Bold.ttf") format('truetype'); } @font-face { font-family: 'NumbersFont'; src: url("../assets/fonts/NumbersRegular.otf") format('opentype'); } @font-face { font-family: 'NumbersBold'; src: url("../assets/fonts/NumbersBold.otf") format('opentype'); } @font-face { font-family: 'Formula1Dark'; src: url("../assets/fonts/Formula1-Display-Black.ttf") format('truetype'); } .bold-font { font-family: 'Formula1Bold' !important; } .numbersFont { font-family: 'NumbersFont' !important; } html { background-color: var(--background, #111111); } body { background-color: var(--background) !important; height: 100vh; overflow-x: hidden; transition: border-color 0.15s, box-shadow 0.15s, background-color 0.15s; } .font { font-family: 'Formula1'; } #byname { text-decoration: none; color: var(--separator); transition: color 0.15s; } body.og-theme .footer { border-top: 1px solid var(--slight-contrast); } .footer { position: fixed; bottom: 0; left: 0; width: 100%; background-color: transparent; color: var(--dark-text); text-align: center; padding: 5px 0; font-size: 16px; z-index: 200; display: flex; flex-direction: row; justify-content: space-between; max-height: 35px; overflow-y: clip; border-top-left-radius: 10px; border-top-right-radius: 10px; transition: opacity 0.15s, transform 0.15s; } .scroll-wrapper { overflow-y: auto; height: 92%; } .scroll-wrapper::-webkit-scrollbar { width: 6px; } .scroll-wrapper::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 6px; } #free-drivers::-webkit-scrollbar, #free-staff::-webkit-scrollbar { width: 4px !important; margin-left: 4px; } #free-drivers::-webkit-scrollbar-thumb, #free-staff::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px !important; } #free-drivers::-webkit-scrollbar-thumb:hover, #free-staff::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } .socials a, .footer i { cursor: pointer !important; padding: 0 7px; transition: color 0.15s; color: var(--dark-text); text-decoration: none; } body.og-theme .socials a, body.og-theme .footer i { color: var(--footer-og); } .save-info { display: flex; justify-content: center; cursor: default; position: absolute; left: 50%; transform: translateX(-50%); } .socials { display: flex; justify-content: start; } .save-button { position: fixed; bottom: 60px; right: -146px; z-index: 1000; padding: 13px 25px 13px 15px; background-color: var(--text-general); color: var(--negative-text); border: none; border-radius: 5px; cursor: pointer; transition: right 0.15s, background-color 0.15s, color 0.15s; border-radius: 10px; font-size: 18px; box-shadow: var(--shadow-m); } .save-button:active { transform: scale(0.95); /* Ajusta esto a tu gusto */ box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2); } @keyframes slideAndColorChange { 0% { right: -146px; background-color: var(--text-general); } 10% { right: -17px; background-color: var(--new-primary); box-shadow: 0 0 8px var(--new-primary); } 45% { right: -17px; background-color: var(--new-primary); box-shadow: 0 0 8px var(--new-primary); } 55% { right: -17px; background-color: var(--new-secondary); box-shadow: 0 0 8px var(--new-secondary); } 90% { right: -17px; background-color: var(--new-secondary); box-shadow: 0 0 8px var(--new-secondary); } 100% { right: -146px; background-color: var(--text-general); } } .save-button.first-show { animation: slideAndColorChange 3s forwards; } body.og-theme .important-text { color: var(--new-secondary); } .important-text { color: var(--new-primary-dark); } .patreon-changes-text { display: flex; flex-direction: column; gap: 10px; } .patreon-text { color: var(--support-me); } .patreon-paragraph { display: flex; flex-direction: column; gap: 10px; } /* Developer tools (localhost only) */ .devtools-window { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); width: min(380px, calc(100vw - 28px)); min-height: 220px; background: linear-gradient(180deg, rgba(18, 22, 34, 0.96), rgba(7, 11, 18, 0.96)), radial-gradient(circle at top right, rgba(76, 201, 240, 0.12), transparent 38%); border: 1px solid rgba(125, 249, 255, 0.18); border-radius: 18px; padding: 14px; color: #d9fdf6; box-shadow: 0 24px 80px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.03) inset, 0 0 28px rgba(0, 255, 179, 0.08); z-index: 6000; display: none; overflow: hidden; backdrop-filter: blur(18px); font-family: "Cascadia Code", "Fira Code", "JetBrains Mono", "Consolas", "Courier New", monospace; } .devtools-window::before { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0, 255, 179, 0.08), transparent 30%, transparent 70%, rgba(122, 92, 255, 0.08)), repeating-linear-gradient(180deg, rgba(255, 255, 255, 0.03) 0 1px, transparent 1px 4px); opacity: 0.8; pointer-events: none; } .devtools-window::after { content: ""; position: absolute; inset: 1px; border-radius: 17px; border: 1px solid rgba(255, 255, 255, 0.05); pointer-events: none; } .devtools-window.show { display: block; } .devtools-header { display: flex; align-items: center; gap: 12px; cursor: move; user-select: none; margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid rgba(125, 249, 255, 0.12); position: relative; z-index: 1; } .devtools-window.dragging { user-select: none; box-shadow: 0 30px 90px rgba(0, 0, 0, 0.65), 0 0 40px rgba(0, 255, 179, 0.12); } .devtools-chrome { display: flex; align-items: center; gap: 6px; } .devtools-dot { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 12px currentColor; } .devtools-dot.is-red { color: #ff5f57; background: #ff5f57; } .devtools-dot.is-yellow { color: #febc2e; background: #febc2e; } .devtools-dot.is-green { color: #28c840; background: #28c840; } .devtools-title { font-size: 15px; font-weight: 700; letter-spacing: 0.22em; color: #f5fffd; text-shadow: 0 0 14px rgba(0, 255, 179, 0.2); } .devtools-status { margin-left: auto; color: rgba(140, 250, 223, 0.72); font-size: 11px; letter-spacing: 0.12em; } .devtools-close { border: 1px solid rgba(255, 95, 87, 0.25); background: rgba(255, 95, 87, 0.12); color: #ffd8d5; font-size: 18px; line-height: 1; width: 34px; height: 34px; border-radius: 10px; padding: 0; cursor: pointer; transition: transform 0.14s ease, background-color 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease; position: relative; z-index: 2; } .devtools-close:hover { background: rgba(255, 95, 87, 0.2); border-color: rgba(255, 95, 87, 0.45); box-shadow: 0 0 18px rgba(255, 95, 87, 0.18); } .devtools-close:active { transform: scale(0.95); } .devtools-close:focus-visible { outline: 1px solid rgba(255, 255, 255, 0.28); outline-offset: 2px; } .devtools-body { display: flex; flex-direction: column; gap: 12px; position: relative; z-index: 1; } .devtools-hint { padding: 10px 12px; border-radius: 12px; background: rgba(255, 255, 255, 0.03); border: 1px solid rgba(125, 249, 255, 0.08); color: rgba(184, 255, 239, 0.78); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; } .devtools-action { border: 1px solid rgba(125, 249, 255, 0.12); border-radius: 14px; padding: 14px 16px; background: linear-gradient(135deg, rgba(9, 28, 26, 0.92), rgba(20, 25, 44, 0.88)); color: #effffc; cursor: pointer; transition: transform 0.12s ease, border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease; text-align: left; font-size: 13px; font-weight: 600; letter-spacing: 0.04em; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04), 0 10px 24px rgba(0, 0, 0, 0.22); } .devtools-action:hover { border-color: rgba(125, 249, 255, 0.28); background: linear-gradient(135deg, rgba(13, 40, 37, 0.95), rgba(26, 32, 56, 0.92)); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 24px rgba(0, 255, 179, 0.08), 0 12px 30px rgba(0, 0, 0, 0.28); } .devtools-action:active { transform: translateY(1px) scale(0.99); } .devtools-action:disabled { opacity: 0.65; cursor: default; box-shadow: none; } @media (max-width: 540px) { .devtools-window { left: 14px; right: 14px; top: 14px; width: auto; transform: none; } .devtools-header { flex-wrap: wrap; } .devtools-status { order: 4; width: 100%; margin-left: 0; } } .patreon-plain-text { color: var(--modal-text) } .patreon-perks { display: flex; flex-direction: row; justify-content: space-between; align-items: flex-start; } #patreonKeyGeneral { margin-top: 10px; } .patreon-button { display: flex; flex-direction: row; gap: 5px; align-items: center; transition: background-color 0.15s, color 0.15s; padding: 8px 15px; border-radius: 7px; cursor: pointer; width: max-content; text-decoration: none; } a:has(.patreon-button) { text-decoration: none; } .patreon-button:hover { background-color: var(--text-general); } .patreon-button:hover .patreon-button-text, patreon-button:active .patreon-button-text { color: var(--negative-text); } .patreon-button:hover .patreon-button-logo, .patreon-button:active .patreon-button-logo { background-color: var(--negative-text); } .patreon-button:active { transform: scale(0.98); } .patreon-button-logo { width: 1em; height: 1em; -webkit-mask: url('/assets/images/patreonLogo.svg') no-repeat center; mask: url('/assets/images/patreonLogo.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); transition: all 0.15s; } .patreon-button-text { color: var(--text-general); font-size: 18px; text-transform: uppercase; transition: all 0.15s; text-decoration: none; } .perks-list { display: flex; flex-direction: column; gap: 5px; } .danger-text { color: var(--negative-general); } .save-button .save-label { transition: opacity 0.15s; cursor: pointer; } .save-button i { transition: opacity 0.15s; cursor: pointer; } .save-button .save-label { opacity: 0; } .save.button i { opacity: 1; } .save-button:hover i, .save-button.first-show i { opacity: 0; } .save-button:hover .save-label, .save-button.first-show .save-label { opacity: 1; } .save-button:hover { right: -17px; background-color: var(--new-primary); } .save-button i { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); } .twenty-three, .twenty-four { color: var(--dark-text); transition: color 0.15s, border-color 0.15s; width: 50px; position: relative; } .twenty-three::before { content: ""; display: block; width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: radial-gradient(circle at 100% 180%, var(--new-secondary), transparent 70%); opacity: 0; transition: opacity 0.15s; z-index: -1; } .twenty-four::before { content: ""; display: block; width: 100%; height: 100%; position: absolute; top: 0; left: 0; background: radial-gradient(circle at 0% 180%, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.15s; z-index: -1; } .twenty-three { border-right: 1px solid var(--dark-text); } .twenty-four { border-left: 1px solid var(--dark-text); } .twenty-three.activated { color: var(--text-general); border-color: var(--new-secondary); } .twenty-four.activated { color: var(--text-general); border-color: var(--new-primary); } .twenty-three.activated::before { opacity: 1; } .twenty-four.activated .front-gradient, .twenty-three.activated .front-gradient { text-shadow: 0 0 5px black; } .twenty-four.activated::before { opacity: 1; } .socials a:hover, .footer i:hover { color: var(--dark-text); } .socials a { display: inline-block; overflow: hidden; max-width: 27px; white-space: nowrap; transition: max-width 0.35s ease-in-out, color 0.15s; } .socials a:hover { max-width: 240px; } .update-info { transition: color 0.15s, opacity 0.15s; text-decoration: none; color: var(--dark-text); cursor: default; padding-right: 10px; } .update-info.hide { opacity: 0; width: 0; padding-right: 0; } .update-info.hide::before { width: 0; } .version-panel { transition: border 0.15s, opacity 0.15s, color 0.15s, text-shadow 0.15s; cursor: pointer; padding: 0 5px; border-left: 2px solid transparent; background-color: var(--background); z-index: 3; will-change: border; } .version-panel.nightly { color: var(--new-secondary) !important; } .version-panel.nightly:hover { color: var(--text-selected-secondary) !important; } .status:has(.footer-notification.show) .version-panel { border-left: 2px solid var(--separator); } .footer-notification { transition: transform 0.22s ease-out, color 0.22s ease-out; padding-right: 5px; z-index: 1; color: var(--positive-general); } .footer-notification.error { color: var(--negative-general); } .footer-notification.error a { color: color-mix(in srgb, var(--negative-general) 60%, white); } .footer-notification.error a:hover { color: color-mix(in srgb, var(--negative-general) 80%, white); } .footer-notification:not(.show) { transform: translateX(125%); } .version-panel:hover { color: var(--text-selected); } .dev-console { position: absolute; top: 0; left: 0; width: 100%; background-color: var(--console-transparent); color: var(--text-general); border: none; font-family: 'Courier New', Courier, monospace; z-index: 1000; } .dev-console:focus-visible { outline: none; } .bi-exclamation-diamond { color: var(--negative-general); } .bi-exclamation-lg, .bi-cloud-download { color: #d3d393; cursor: pointer; } .bi-exclamation-lg:hover, .bi-cloud-download:hover { color: #FFFF99; } a.bi-reddit:hover { color: var(--reddit); } a.bi-twitter-x:hover, #byname:hover { color: var(--twitter); } a.bi-cup-hot-fill:hover { color: var(--support-me); } a.bi-discord:hover { color: var(--discord); } a.bi-linkedin:hover { color: var(--new-secondary); } a.bi-github:hover { color: var(--new-primary); } i.bi-file-text:hover { color: var(--white-general); } .status { display: flex; flex-direction: row; justify-content: flex-end; } .awaiting { color: var(--awaiting-color); } .rounded-checkbox { display: inline-flex; align-items: center; cursor: pointer; border-radius: 5px; margin-top: 10px; border: 1px solid #adb5bd; min-width: 20px; min-height: 20px; } .title { text-align: center; color: var(--white-general); font-size: 26px; padding: 5px 0 5px 0px; flex: 1; } .title-buttons-left { position: absolute; gap: 10px; padding-left: 14px; display: flex; align-items: center; } .stats-add-controls { display: flex; align-items: center; gap: 10px; } #addStaffButton { white-space: nowrap; } .title-buttons-right { position: absolute; right: 0; gap: 10px; padding-right: 14px; display: flex; align-items: center; } .title-buttons-left .custom-dropdown { margin: 0 !important; } .standings-list { width: 65px; text-align: center; } .standings-list-engine { color: var(--engine-table-name); } .transfer-top-panel, .general-top-panel { display: flex; justify-content: space-between; align-items: center; } .general-top-panel .custom-dropdown { margin: 0 !important; } .transfer-title { flex: 1; text-align: center; } .add-contract { border: 2px dashed var(--disabled-text); color: var(--disabled-text); display: flex; flex-direction: row; align-items: center; justify-content: space-between; border-radius: 8px; transition: color 0.15s, border 0.15s, transform 0.15s; padding: 8px; font-size: 16px; } .add-contract.enabled { border: 2px dashed var(--dark-text); color: var(--dark-text); } .add-contract:not(.enabled) .button-with-icon { pointer-events: none; } .add-contract.enabled .button-with-icon { cursor: pointer; } #teamContractButton { margin-top: 0 !important; } #futureContractTitle { border-top: 2px solid var(--separator); } #yearMenu{ min-width: 140px; } .year-and-break { display: flex; flex-direction: row; align-items: baseline; } .contract-title { display: flex; flex-direction: row; justify-content: space-between; align-items: center; font-size: 20px; font-family: 'Formula1Bold'; color: var(--white-general); margin-bottom: 10px; } .auto-contract { color: var(--text-general); font-size: 16px; padding-right: 2rem; position: absolute; right: 0; } .transfer-right-actions { right: 14px; gap: 10px; display: flex; align-items: center; } .transfer-right-actions .auto-contract { position: static; right: auto; padding-right: 0; } .lineups-season-pills-top { display: flex; align-items: center; margin-right: 2px; } .lineups-season-pills-top .generalPills { padding: 4px 10px !important; min-width: 70px; } .lineups-view { background-color: transparent; min-height: 670px; height: 100%; } .lineups-view-header { display: flex; justify-content: space-between; align-items: center; gap: 10px; flex-wrap: wrap; } .lineups-season-pills { display: inline-flex; align-items: center; gap: 4px; background-color: var(--elements); border-radius: 999px; padding: 4px; box-shadow: var(--shadow-s); } .lineups-pill { border: none; border-radius: 999px; background-color: transparent; color: var(--text-general); font-size: 14px; padding: 5px 14px; transition: background-color 0.15s, color 0.15s; } .lineups-pill:hover { background-color: var(--table-hover); color: var(--white-general); } .lineups-pill.active { background-color: var(--text-general); color: var(--negative-text); } .lineups-circle-wrap { min-height: 0; display: flex; justify-content: center; align-items: center; overflow: hidden; height: 100%; } .lineups-circle { position: relative; width: 90%; min-width: 0; min-height: 760px; } .lineups-team-logo { width: 104px; height: 46px; display: flex; align-items: center; justify-content: center; overflow: hidden; margin-bottom: 0; position: relative; z-index: 2; } .lineups-team-logo img { width: 46px; height: 46px; object-fit: contain; } .lineups-team-logo > *:not(img) { transform: scale(1.16); transform-origin: center; } .lineups-team-drivers { margin-top: 8px; padding-top: 6px; border-top: 1px solid color-mix(in srgb, var(--separator) 72%, transparent); display: grid; grid-template-columns: 1fr 1fr; gap: 6px; position: relative; z-index: 1; } .lineups-team-card { --lineup-accent: var(--new-primary); position: absolute; width: 272px; transform: translate(-50%, -50%); background: linear-gradient(165deg, color-mix(in srgb, var(--lineup-accent) 18%, var(--elements)) 0%, color-mix(in srgb, var(--lineup-accent) 10%, var(--elements)) 34%, color-mix(in srgb, var(--elements) 95%, black 5%) 100%); border-radius: 10px; box-shadow: 0 11px 24px rgba(0, 0, 0, 0.36), inset 0 1px 0 rgba(255, 255, 255, 0.04); padding: 8px 10px 9px; color: var(--text-general); transition: none; cursor: default; overflow: hidden; } .lineups-team-card::before { content: ""; position: absolute; inset: 0; background: radial-gradient(120% 85% at -10% -18%, color-mix(in srgb, var(--lineup-accent) 30%, transparent), transparent 55%); pointer-events: none; } .lineups-team-card::after { content: none; } .lineups-team-header { display: flex; align-items: center; justify-content: center; position: relative; min-height: 46px; padding-top: 1px; z-index: 1; overflow: hidden; } @media (max-height: 940px) { .lineups-team-card{ padding: 5px 10px 9px; } .lineups-team-drivers{ margin-top: 4px; padding-top: 3px; } .lineups-team-logo { height: 42px; } .lineups-team-header { min-height: 42px; } .lineups-team-logo img { width: 42px; height: 42px; object-fit: contain; } .lineups-team-logo > *:not(img) { transform: scale(1.06); transform-origin: center; } } .lineups-team-name { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -48%) scaleX(1); transform-origin: center center; width: 124%; text-align: center; text-transform: uppercase; font-size: 28px; line-height: 1; letter-spacing: 0.9px; color: color-mix(in srgb, var(--white-general) 72%, var(--lineup-accent) 28%); opacity: 0.13; text-shadow: none; overflow: hidden; white-space: nowrap; pointer-events: none; z-index: 1; } .lineups-team-driver { font-size: 11px; color: color-mix(in srgb, var(--text-general) 95%, white 5%); text-align: center; background-color: color-mix(in srgb, var(--background) 28%, transparent); border-radius: 4px; padding: 4px 4px; position: relative; isolation: isolate; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.05; min-height: 34px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .lineups-team-driver > *:not(.lineups-driver-number) { position: relative; z-index: 1; } .lineups-driver-number { position: absolute; right: 4px; top: 1px; font-size: 20px; line-height: 1; color: color-mix(in srgb, var(--white-general) 70%, var(--lineup-accent) 30%); opacity: 0.16; pointer-events: none; z-index: 0; font-family: 'NumbersFont'; } .lineups-driver-first { display: block; font-size: 10px; opacity: 0.92; } .lineups-driver-last { display: block; font-size: 13px; letter-spacing: 0.2px; } .to-be-decided{ color: var(--text-tertiary); } .lineups-team-driver.next-contract { color: var(--new-primary); } .lineups-team-card.lineups-custom-team { box-shadow: 0 12px 28px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.04); } .lineups-center-title { position: absolute; top: 47%; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: 'Formula1Dark'; color: color-mix(in srgb, var(--white-general) 95%, var(--new-primary) 5%); text-transform: uppercase; letter-spacing: 1.4px; line-height: 0.9; pointer-events: none; text-shadow: 0 12px 28px rgba(0, 0, 0, 0.55); z-index: 0; opacity: 0.96; } .lineups-center-year { display: block; text-align: center; white-space: nowrap; font-size: clamp(66px, 8vw, 130px); line-height: 0.78; } .lineups-center-label { display: block; text-align: center; white-space: nowrap; font-size: clamp(24px, 5vw, 88px); line-height: 0.88; } .lineups-center-label.lineups{ display: block; text-align: center; white-space: nowrap; font-size: clamp(18px, 3.6vw, 94px); line-height: 0.88; } .freeze-mentality { color: var(--text-general); font-size: 20px; padding-right: 2rem; } /* Base */ input[role="switch"] { cursor: pointer; box-shadow: none; background-color: transparent !important; transition: border-color 0.15s, background-color 0.15s; border: 2px solid var(--switch-border-off) !important; /* clave: necesitamos posicionar el pseudo-thumb */ position: relative; /* clave: desactivamos el “ball” de bootstrap (background-image) */ --bs-form-switch-bg: none !important; } /* Si no quieres glow de focus */ input[role="switch"]:focus { box-shadow: none; } /* THUMB: un solo SVG (thumb-general.svg) coloreable */ input[role="switch"]::after { content: ""; position: absolute; top: 50%; transform: translateY(-50%); /* tamaño del thumb: ajusta si tu switch es más grande/pequeño */ width: 0.85rem; height: 0.85rem; /* posición OFF */ left: 0.01rem; /* el SVG solo define la forma */ -webkit-mask: url('../assets/images/thumb-general.svg') no-repeat center / contain; mask: url('../assets/images/thumb-general.svg') no-repeat center / contain; /* color del thumb (OFF por defecto) */ background-color: var(--switch-border-off); transition: left 0.15s, background-color 0.15s; } /* OFF */ input[role="switch"]:not(:checked) { border-color: var(--switch-border-off) !important; } /* ON */ input[role="switch"]:checked { border-color: var(--switch-border-on) !important; background-color: transparent !important; } /* mover thumb a la derecha cuando está ON */ input[role="switch"]:checked::after { /* calc: 100% - thumbWidth - margenIzq */ left: calc(100% - 0.85rem - 0.01rem); background-color: var(--switch-border-on); } /* Variables por tema */ body.light-theme { --switch-thumb-color-off: #b0b0b0; /* gris off */ --switch-thumb-color-on: var(--new-primary);/* on */ --switch-thumb-color-focus: var(--new-primary); } body:not(.light-theme) { --switch-thumb-color-off: #555; /* gris oscuro off */ --switch-thumb-color-on: var(--new-primary); --switch-thumb-color-focus: var(--new-primary); } input[type="checkbox"] { border: none; } input[type="checkbox"]:focus { box-shadow: none; } .main-team-changes { display: flex; flex-direction: column; } .team-changes-grid{ display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px 10px; margin-top: 5px; align-items: center; } .team-changes-grid .logo-and-name{ font-size: 12px; height: 30px; } .team-changes-grid .logo-and-name .team-name{ padding-left: 0px; } .team-changes-grid [data-resizable="true"] { transform: scale(0.8); } .team-change { display: flex; flex-direction: column; align-items: flex-start; } .team-change-button { text-align: end; align-items: end; display: flex; justify-content: flex-end; } .info-modal-section { display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .texts-and-clear { display: flex; flex-direction: row; gap: 15px; align-items: flex-start; justify-content: flex-start; } .texts-and-clear .fit-button { padding: 0; } .H2H-text span.text-main { color: var(--new-primary); } .graph-text span.text-main { color: var(--new-secondary); } .h2h-highlight { animation: h2h-animation 0.15s forwards; } .graph-highlight { animation: graph-animation 0.15s forwards; } @keyframes h2h-animation { 0%, 100% { color: var(--text-general); } 50% { color: var(--text-selected); } } @keyframes graph-animation { 0%, 100% { color: var(--text-general); } 50% { color: var(--text-selected-secondary); } } .annotations { color: var(--text-general); font-size: 16px; padding-right: 1rem; } .number-details { display: flex; flex-direction: row; align-items: baseline; width: 100%; justify-content: flex-end; gap: 15px; } #d1Menu, #d2Menu, #teamMenu, #raceProbMenu { max-height: 200px; overflow-y: scroll; } #numberMenu { max-height: 200px; overflow-y: scroll; } #numberMenu::-webkit-scrollbar, #d1Menu::-webkit-scrollbar, #d2Menu::-webkit-scrollbar, #teamMenu::-webkit-scrollbar, #teamContractMenu::-webkit-scrollbar, #raceMenu::-webkit-scrollbar, #raceProbMenu::-webkit-scrollbar, #nationalityMenu::-webkit-scrollbar, .prob-viewer-data::-webkit-scrollbar, #patchNotesBody::-webkit-scrollbar, .parts-list::-webkit-scrollbar, .engine-modal-body::-webkit-scrollbar, .news-article::-webkit-scrollbar, .main-second-viewer::-webkit-scrollbar, .news-edit-textarea::-webkit-scrollbar, #juniorTeamContractMenu::-webkit-scrollbar, .poles-comparison::-webkit-scrollbar, .q3-comparison::-webkit-scrollbar, .q2-comparison::-webkit-scrollbar, .wins-drivers-list::-webkit-scrollbar, .bento-driver-standings::-webkit-scrollbar, #sessionResultsGpMenu::-webkit-scrollbar, #engineMenu::-webkit-scrollbar, .bento-team-standings::-webkit-scrollbar, .quali-comparison::-webkit-scrollbar, .race-comparison::-webkit-scrollbar, .script-view::-webkit-scrollbar { width: 4px; } #numberMenu::-webkit-scrollbar-thumb, #d1Menu::-webkit-scrollbar-thumb, #d2Menu::-webkit-scrollbar-thumb, #teamMenu::-webkit-scrollbar-thumb, #teamContractMenu::-webkit-scrollbar-thumb, #raceMenu::-webkit-scrollbar-thumb, #raceProbMenu::-webkit-scrollbar-thumb, #nationalityMenu::-webkit-scrollbar-thumb, .prob-viewer-data::-webkit-scrollbar-thumb, #patchNotesBody::-webkit-scrollbar-thumb, .parts-list::-webkit-scrollbar-thumb, .engine-modal-body::-webkit-scrollbar-thumb, .news-article::-webkit-scrollbar-thumb, .main-second-viewer::-webkit-scrollbar-thumb, .news-edit-textarea::-webkit-scrollbar-thumb, #juniorTeamContractMenu::-webkit-scrollbar-thumb, .poles-comparison::-webkit-scrollbar-thumb, .q3-comparison::-webkit-scrollbar-thumb, .q2-comparison::-webkit-scrollbar-thumb, .wins-drivers-list::-webkit-scrollbar-thumb, .bento-driver-standings::-webkit-scrollbar-thumb, .bento-team-standings::-webkit-scrollbar-thumb, #sessionResultsGpMenu::-webkit-scrollbar-thumb, #engineMenu::-webkit-scrollbar-thumb, .quali-comparison::-webkit-scrollbar-thumb, .race-comparison::-webkit-scrollbar-thumb, .script-view::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px; } #numberMenu::-webkit-scrollbar-thumb:hover, #d1Menu::-webkit-scrollbar-thumb:hover, #d2Menu::-webkit-scrollbar-thumb:hover, #teamMenu::-webkit-scrollbar-thumb:hover, #teamContractMenu::-webkit-scrollbar-thumb:hover, #raceMenu::-webkit-scrollbar-thumb:hover, #raceProbMenu::-webkit-scrollbar-thumb:hover, #nationalityMenu::-webkit-scrollbar-thumb:hover, .prob-viewer-data::-webkit-scrollbar-thumb:hover, #patchNotesBody::-webkit-scrollbar-thumb:hover, .parts-list::-webkit-scrollbar-thumb:hover, .engine-modal-body::-webkit-scrollbar-thumb:hover, .news-article::-webkit-scrollbar-thumb:hover, .main-second-viewer::-webkit-scrollbar-thumb:hover, .news-edit-textarea::-webkit-scrollbar-thumb:hover, #juniorTeamContractMenu::-webkit-scrollbar-thumb:hover, .poles-comparison::-webkit-scrollbar-thumb:hover, .q3-comparison::-webkit-scrollbar-thumb:hover, .q2-comparison::-webkit-scrollbar-thumb:hover, .wins-drivers-list::-webkit-scrollbar-thumb:hover, .bento-driver-standings::-webkit-scrollbar-thumb:hover, #sessionResultsGpMenu::-webkit-scrollbar-thumb:hover, #engineMenu::-webkit-scrollbar-thumb:hover, .bento-team-standings::-webkit-scrollbar-thumb:hover, .quali-comparison::-webkit-scrollbar-thumb:hover, .race-comparison::-webkit-scrollbar-thumb:hover, .script-view::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } #engineMenu { max-height: 300px; overflow-y: scroll; } #allDrivers { overflow-y: hidden; } .dropdown-menu { background-color: var(--dropdown-back) !important; border: none !important; box-shadow: 0 2px 6px 1px black; margin-top: 1px !important; z-index: 9999 !important; } .dropdown-menu .dropdown-item { cursor: pointer; } .dropdown-menu a { background-color: var(--dropdown-back) !important; color: var(--white-general) !important; border-bottom: 2px solid transparent !important; transition: background-color 0.15s, color 0.15s, border-bottom 0.15s; } .dropdown-menu a:hover { background-color: var(--dropdown-hover) !important; color: var(--text-selected) !important; border-bottom: 2px solid var(--new-primary) !important; } body.og-theme .dropdown-menu a:hover { color: var(--white-general) !important; border-bottom: 2px solid #dedde6 !important; } .dropdown-manu a::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.15s ease; pointer-events: none; } .dropdown-menu a:hover::before { opacity: 1; } .dropdown-menu a:not(:hover)::before { transition-delay: 0s; } .modal-subtitle { color: var(--text-general); } .upper-section-stats { display: flex; padding-right: 16px; background: var(--elements); border-top-left-radius: 10px; border-top-right-radius: 10px; box-shadow: var(--shadow-s); align-items: center; padding: 18px 25px; height: 116px; gap: 20px; justify-content: space-between; position: relative; } .special-overall.cloned { margin-left: auto; } /*Gradient from left to right with new primary to transparent*/ .upper-section-stats.comparison-active:has(.cloned-separator)::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to right, var(--new-primary) -39%, transparent 36%); z-index: 1; border-top-left-radius: 10px; } .upper-section-stats.comparison-active:has(.cloned-separator)::before { content: ''; position: absolute; top: 0; right: 0; width: 100%; height: 100%; background: linear-gradient(to left, var(--new-secondary) -39%, transparent 36%); z-index: 1; border-top-right-radius: 10px; } .stats-radar-wrap { position: relative; } /* capa HTML encima del canvas */ .radar-overlay { position: absolute; inset: 0; pointer-events: none; font-family: Formula1Bold; line-height: 1; } .radar-overlay .tick { position: absolute; transform: translate(-50%, -50%); text-align: center; white-space: nowrap; /* si se corta en bordes, añade un poco de padding externo al chart: options.layout.padding */ } .radar-overlay .values { display: inline-flex; gap: 6px; justify-content: center; font-size: 11px; } .radar-overlay .v1 { color: var(--new-primary); } .radar-overlay .v2 { color: var(--new-secondary); } .radar-overlay .lab { display: block; font-size: 12px; color: var(--text-secondary); margin-top: 2px; } .upper-section-stats.comparison-active:has(.cloned-separator) .name-and-info { flex-grow: 0; max-width: none; } .upper-section-stats.comparison-active:has(.cloned-separator) { gap: 15px; } .upper-section-stats.comparison-active:has(.cloned-separator) .age-text { margin-left: 0 !important; } .upper-section-stats.comparison-active:has(.cloned-separator) .bi-pencil-fill { display: none !important; pointer-events: none; } .upper-section-stats.comparison-active:has(.cloned-separator) .name-and-code { gap: 10px; } .upper-section-stats.comparison-active:has(.cloned-separator) .hiddable-text, .upper-section-stats.comparison-active:has(.cloned-separator) .age-buttons, .upper-section-stats.comparison-active:has(.cloned-separator) .retirement-buttons { display: none; pointer-events: none; } .title-and-details.cloned { align-items: flex-end; } .title-and-details.cloned .age-holder { align-items: flex-end; } .title-and-details.cloned .age, .title-and-details.cloned .retirement-age { flex-direction: row-reverse; } .retry-with-lite { margin-top: 10px; } .button-with-icon { display: flex; flex-direction: row; align-items: center; gap: 10px; cursor: pointer; color: var(--text-secondary); transition: color 0.15s, background-color 0.15s; font-size: 16px; padding: 3px 7px; border-radius: 5px; } .button-with-icon.close-modal{ padding: 3px 7px; } #compDataButton.button-with-icon { color: var(--text-general); } #compDataButton.button-with-icon:hover { color: var(--new-primary); } .button-with-icon:hover { color: var(--new-primary); background-color: var(--new-primary-transparent) } #patreonLoginButton i::before { transition: background-color 0.15s; } #patreonLoginButton:hover i::before { background-color: var(--new-primary); } .user-name-and-logout-tool { position: relative; } .userToolMenu { display: flex; flex-direction: column; position: absolute; top: 40px; right: 0px; border-radius: 7px; width: max-content; gap: 2px; padding: 8px 5px; opacity: 1; z-index: 200; transform: translateY(0); pointer-events: auto; background-color: var(--elements); box-shadow: var(--shadow-m); transition: opacity 0.15s, transform 0.15s; } .userToolMenu.hidden { opacity: 0; transform: translateY(-10px); pointer-events: none; } .button-with-icon.active { color: var(--new-primary); background-color: var(--new-primary-transparent); } .button-with-icon:active { color: var(--new-primary); background-color: var(--new-primary-transparent); transform: scale(0.97); } .button-with-icon#patreonStatus:hover { color: var(--text-secondary); background-color: transparent; cursor: default; } .button-with-icon#patreonStatus:active { transform: none; } .button-with-icon#patreonStatus i.bi-custom-patreon::before { background-color: var(--text-secondary); } .button-with-icon#patreonStatus:hover i.bi-custom-patreon::before { background-color: var(--text-secondary); } .comparing-tag { font-size: 12px; padding: 2px 6px; border-radius: 4px; text-transform: capitalize; font-weight: bold; margin-left: 8px; } .bar-container.comparing { flex-direction: column; align-items: flex-start; gap: 1px; } input.comparing-tag { font-weight: normal !important; font-size: 18px !important; color: var(--text-general) !important; padding: 2px !important; line-height: 1.5; margin: 0 5px !important; } .comparing-tag.primary { background-color: var(--new-primary-transparent); color: var(--new-primary); } .comparing-tag.secondary { background-color: var(--new-secondary-transparent); color: var(--new-secondary); } .comparing-tag.ct { background-color: color-mix(in srgb, var(--custom-team-primary) 35%, transparent); color: var(--custom-team-primary); } .comparing-tag.fe { background-color: color-mix(in srgb, var(--ferrari-primary) 35%, transparent); color: var(--ferrari-primary); } .comparing-tag.me { background-color: color-mix(in srgb, var(--mercedes-primary) 35%, transparent); color: var(--mercedes-primary); } .comparing-tag.rb { background-color: color-mix(in srgb, var(--redbull-primary) 35%, transparent); color: var(--redbull-primary); } .comparing-tag.mc { background-color: color-mix(in srgb, var(--mclaren-primary) 35%, transparent); color: var(--mclaren-primary); } .comparing-tag.wi { background-color: color-mix(in srgb, var(--williams-primary) 35%, transparent); color: var(--williams-primary); } .comparing-tag.ha { background-color: color-mix(in srgb, var(--haas-primary) 35%, transparent); color: var(--haas-primary); } .comparing-tag.as { background-color: color-mix(in srgb, var(--aston-primary) 35%, transparent); color: var(--aston-primary); } .comparing-tag.al { background-color: color-mix(in srgb, var(--alpine-primary) 35%, transparent); color: var(--alpine-primary); } .comparing-tag.af { background-color: color-mix(in srgb, var(--alfa-primary) 35%, transparent); color: var(--alfa-primary); } .comparing-tag.at { background-color: color-mix(in srgb, var(--alphatauri-primary) 35%, transparent); color: var(--alphatauri-primary); } .upper-section-stats.showing-driver .name-and-info { max-width: calc(42% - 20px); } .name-and-info { display: flex; flex-direction: column; gap: 10px; flex-grow: 1; z-index: 3; } .name-and-info.cloned { align-items: flex-end; } .name-and-info.cloned .name-and-code, .name-and-info.cloned .flag-and-team { flex-direction: row-reverse; } .stats-header-separator { width: 2px; background-color: var(--separator-light); height: 100%; z-index: 3; } .name-and-code { display: flex; flex-direction: row; height: 36px; align-items: center; gap: 30px; z-index: 3; } .custom-separator { width: 2px; background-color: var(--separator-light); height: 85%; z-index: 3; } .flag-and-team { display: flex; flex-direction: row; align-items: center; gap: 15px; height: 30px; z-index: 3; } .image-and-text { display: flex; flex-direction: row; align-items: center; gap: 10px; z-index: 3; } .nationality-dropdown { display: flex; align-items: center; gap: 10px; height: 32px; padding: 0 10px; border-radius: 8px; background: transparent; max-width: 200px; } .nationality-dropdown:disabled { opacity: 0.75; cursor: default; } .nationality-dropdown .flag-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: left; } #nationalityMenu { max-height: 320px; overflow: auto; max-width: 200px; scrollbar-width: thin; scrollbar-color: var(--white-general) transparent; } .redesigned-dropdown-item.nationality-item { display: flex; align-items: center; gap: 10px; justify-content: flex-start; width: 100%; max-width: 100%; } .nationality-item span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .nationality-item img { width: 22px; height: 14px; border-radius: 3px; object-fit: cover; } .driver-info-driver-flag { width: 40px; height: auto; border-radius: 4px; height: 20px; object-fit: cover; } .driver-info-team-logo { width: 34px; height: auto; border-radius: 5px; } .driver-info-team-logo-masked { height: 24px; -webkit-mask: url('/assets/images/logos/mclaren.svg') no-repeat center; mask: url('/assets/images/logos/mclaren.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--mclaren-primary); } .actual-age, .actual-retirement { width: 30px; text-align: center; display: inline-block; } .info-text { color: var(--text-secondary); font-size: 18px; text-transform: capitalize; } #driverStatsTitle { font-size: 26px; width: max-content; position: relative; max-width: 350px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; } #availabilityDetails:has(#superLicenseSwitch.d-none) { justify-content: flex-start; gap: 6px; } #driverCode { font-size: 26px; color: var(--text-secondary); text-transform: uppercase; position: relative; } #driverStatsTitle textarea, #driverCode textarea { resize: none; background: transparent; color: inherit; font: inherit; border: none; border-bottom: 2px solid var(--text-secondary); outline: none; box-sizing: border-box; padding: 0; margin: 0; overflow: hidden; white-space: nowrap; /* evita saltos de línea */ overflow-x: hidden; /* permite hacer scroll horizontal si se pasa del ancho */ overflow-y: hidden; } #editNameButton.bi-check { font-size: 22px; } #editNameButton.bi-check:hover { color: var(--positive-general); background-color: var(--positive-transparent); } #editNameButton { width: 24px; height: 24px; cursor: pointer; border-radius: 5px; display: flex; align-items: center; justify-content: center; transition: background-color 0.15s, color 0.15s; } #editNameButton:hover { background-color: var(--new-primary-transparent); color: var(--new-primary); } #editNameButton:active { background-color: var(--new-primary-transparent); color: var(--new-primary); transform: scale(0.97); } .draft-staff-type-dropdown { min-width: 190px; height: 32px; font-size: 14px; padding: 0 14px; } #genderSwapButton.compact { height: 32px; min-width: 104px; font-size: 14px; padding: 0 12px; white-space: nowrap; } .api-key-section .separator { height: 20px; } .separator-3 { width: 3px; background-color: var(--separator); height: calc(100% - 22px); } .title-and-details { height: 100%; display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; z-index: 3; } .title-and-details.special-overall { align-items: center; } .stats-title { font-size: 17px; color: var(--text-secondary); } .age-holder { display: flex; flex-direction: column; align-items: flex-start; } .age-title { color: var(--text-general); } .age-text { color: var(--text-secondary); margin-left: 12px; } .text-filter { background-color: transparent; color: var(--white-general); border-bottom: 2px solid var(--separator); border-right: 0px; border-left: 0px; border-top: 0px; transition: color 0.15s, border-color 0.15s; height: 36px; padding: 6px; width: 100%; } .text-filter-container { margin-top: 5px; width: 100%; margin-right: 10px; position: relative; } .text-filter-container .bi-x { color: var(--dark-text); cursor: pointer; transition: color 0.15s, transform 0.15s, opacity 0.15s; position: absolute; right: 0px; } .text-filter-container .bi-x:hover { color: var(--new-primary); transform: scale(1.1); } .filter-icon { color: var(--dark-text); font-size: 20px; display: flex; align-items: center; cursor: pointer; transition: color 0.15s; } .new-pills-filters{ padding: 3px 10px !important; border-radius: 6px !important; background-color: transparent; } .new-pills-filters:hover{ background-color: var(--new-primary-transparent); color: var(--new-primary); } .new-pills-filters:active{ transform: scale(0.97); } .new-pills-filters.active{ background-color: var(--new-primary-transparent) !important; color: var(--new-primary) !important; } .new-pills-filters.f2-filter:hover{ color: var(--f2-back); background-color: color-mix(in srgb, var(--f2-back) 30%, transparent); transform: scale(1.05); } .new-pills-filters.f2-filter:active{ transform: scale(0.97); } .new-pills-filters.f2-filter.active{ color: var(--f2-back); background-color: color-mix(in srgb, var(--f2-back) 30%, transparent); } .new-pills-filters.f3-filter:hover{ color: var(--f3-back); background-color: color-mix(in srgb, var(--f3-back) 30%, transparent); transform: scale(1.05); } .new-pills-filters.f3-filter:active{ transform: scale(0.97); } .new-pills-filters.f3-filter.active{ color: var(--f3-back); background-color: color-mix(in srgb, var(--f3-back) 30%, transparent); } .filter-icon:hover { color: var(--new-primary); transform: scale(1.05); } .filter-icon:active { color: var(--new-primary); transform: scale(0.97); } .filter-container { background-color: transparent; border-radius: 10px; box-shadow: 0 0 0 0 var(--box-shadow); transition: background-color 0.15s, box-shadow 0.15s; display: flex; align-items: center; gap: 10px; font-size: 14px; padding: 2px 12px; } .filter-container:not(.focused) .category-filters { pointer-events: none; } .filter-container.focused .filter-icon { color: var(--new-primary); } .category-filters, .order-filters { opacity: 0; transition: opacity 0.15s; } .category-filters:not(.show) a { pointer-events: none; } .category-filters.show, .order-filters.show { opacity: 1; } .category-filters .basic-label { color: var(--dark-text); } #freefilter:hover .pill-line, #freefilterTransfers:hover .pill-line { width: 100%; } #freefilter.active .basic-label, #freefilterTransfers.active .basic-label { color: var(--new-primary); } #freefilter:hover .pill-line, #freefilterTransfers:hover .pill-line { background-color: var(--new-primary); } #freefilter:hover .basic-label, #freefilterTransfers:hover .basic-label { color: var(--new-primary); } #F1filter:hover .pill-line, #F1filterTransfers:hover .pill-line { width: 100%; } #F1filter .pill-line, #F1filterTransfers .pill-line { background-color: var(--white-general); } #F1filter.active .basic-label, #F1filterTransfers.active .basic-label { color: var(--text-general); } #F1filter:hover .pill-line, #F1filterTransfers:hover .pill-line { background-color: var(--text-general); } #F1filter:hover .basic-label, #F1filterTransfers:hover .basic-label { color: var(--text-general); } #F2filter:hover .pill-line, #F2filterTransfers:hover .pill-line { width: 100%; } #F2filter .pill-line, #F2filterTransfers .pill-line { background-color: var(--f2-border); } #F2filter.active .basic-label, #F2filterTransfers.active .basic-label { color: var(--f2-border); } #F2filter:hover .pill-line, #F2filterTransfers:hover .pill-line { background-color: var(--f2-border); } #F2filter:hover .basic-label, #F2filterTransfers:hover .basic-label { color: var(--f2-border); } #F3filter .pill-line, #F3filterTransfers .pill-line { background-color: var(--f3-back); } #F3filter:hover .pill-line, #F3filterTransfers:hover .pill-line { width: 100%; } #F3filter.active .basic-label, #F3filterTransfers.active .basic-label { color: var(--f3-border); } #F3filter:hover .pill-line, #F3filterTransfers:hover .pill-line { background-color: var(--f3-border); } #F3filter:hover .basic-label, #F3filterTransfers:hover .basic-label { color: var(--f3-border); } #StaffLists { margin-left: 14px; } .search-and-order { margin-left: 10px; margin-bottom: 7px; display: flex; align-items: flex-end; justify-content: space-between; } .order-space { margin-right: 20px; width: 23px; height: 35px; position: relative; cursor: pointer; } .search-and-order i { font-size: 24px; transition: opacity 0.15s, transform 0.15s, color 0.15s; color: var(--dark-text); position: absolute; z-index: 1; } .order-space i:hover { transform: scale(1.1); color: var(--text-selected); } .search-and-order i.active { color: var(--new-primary); } .search-and-order i.hidden { opacity: 0; z-index: 0; } .text-filter:hover { border-bottom: 2px solid var(--text-selected); } .text-filter:focus { color: var(--text-selected); border-bottom: 2px solid var(--new-primary); } .text-filter:focus-visible { outline: none; } .color-reader:focus-visible { outline: none; } .retirement-age, .age { display: flex; gap: 3px; } .retirement-buttons, .age-buttons { font-size: 18px; display: flex; flex-direction: row; margin-top: 4px; gap: 5px; margin-left: 5px; } .number-selector-and-number-1 { display: flex; flex-direction: column; align-items: flex-start; } .number-selector { display: flex; flex-direction: row; align-items: center; gap: 9px; } .number-holder { font-size: 20px; color: var(--text-general); width: 32px; } .wc-number { display: flex; flex-direction: column; align-items: flex-end; } .number-buttons { display: flex; flex-direction: row; gap: 5px; } .new-augment-button.transparent { background-color: transparent; } .new-augment-button.transparent:hover { background-color: var(--augment-buttons-bg); } .new-augment-button { background-color: var(--augment-buttons-bg); border-radius: 5px; width: 18px; height: 18px; display: flex; align-items: center; justify-content: center; font-size: 22px; cursor: pointer; transition: background-color 0.15s, color 0.15s; user-select: none; } .new-augment-button.small { font-size: 16px; } .new-augment-button:hover { background-color: var(--augment-buttons-hover); color: var(--new-primary); } .new-augment-button:active { transform: scale(0.95); color: var(--new-primary); } .name-age-and-retired { display: flex; flex-direction: row; gap: 15px; } #numberName { position: relative; } #numberName .name-and-stat { position: absolute; margin-left: 20px; } #numberName .number-details { position: absolute; margin-right: 50px; margin-top: 15px; } .ovr { font-size: 21px; color: var(--disabled-text) } .ovrcombined { display: flex; width: 116px; align-items: baseline; justify-content: space-between; width: 120px; position: relative; gap: 5px; } .wc-number .custom-toggle { margin-left: -20px; } .overall-holder { font-size: 34px; } .upper-panel { display: flex; } .number-and-retirement { display: flex; flex-direction: row; width: 66%; background-color: var(--elements); padding: 10px; border-radius: 10px; margin-right: 20px; margin-top: 10px; margin-bottom: 10px; margin-left: 10px; box-shadow: 0 0 10px 1px var(--box-shadow); } .number-options { width: 66%; } .retirement-options i { color: var(--text-general); border-radius: 7.5px !important; width: 20px !important; height: 20px !important; font-size: 20px !important; margin-left: 5px !important; } @keyframes colorChangeNeg { 0%, 100% { background-color: transparent; color: var(--white-general); } 50% { background-color: transparent; color: var(--negative-general); } } @keyframes colorChangePos { 0%, 100% { background-color: transparent; color: var(--white-general); } 50% { background-color: transparent; color: var(--positive-general); } } .alertNeg { animation: colorChangeNeg 0.15s; margin-bottom: 0px !important; } .alertPos { animation: colorChangePos 0.15s; margin-bottom: 0px !important; } .main-title-old { text-align: left; background-color: var(--tools-general); padding-top: 1rem; padding-bottom: 0.1%; display: flex; width: 100%; justify-content: center; } .cet-menubar { opacity: 0 !important; } .cet-title { font-family: 'Formula1Bold' !important; } .patchNotesPanel { position: fixed; top: 70px; left: 10px; } .patchNotesPanel i { transition: color 0.15s; font-style: normal; color: var(--separator); cursor: pointer; } .patchNotesPanel i:hover { color: var(--white-general); } .notification-panel { position: absolute; top: 56px; right: 0px; color: var(--white-general); width: 530px; height: 116px; display: flex; flex-direction: column; text-align: center; align-items: flex-end; gap: 10px; pointer-events: none; } .notification-line { width: 0%; height: 2px; background-color: var(--white-general); position: relative; bottom: 7px; transition: width 3s linear; } .notification-line.start { width: 100%; } .line-error { width: 100%; height: 2px; position: relative; bottom: 7px; background-color: var(--negative-general) !important; } .custom-toast { background-color: var(--toast-background) !important; color: var(--white-general); border-radius: 10px !important; min-width: 420px !important; z-index: 9999 !important; flex-direction: row !important; position: relative !important; justify-content: space-between !important; } .toast-icon { font-size: 26px; height: 65%; border-right: 2px solid transparent; display: flex; padding: 0 10px; align-items: center; } .custom-toast-body { font-size: 16px; text-align: left; } .toast-icon.success { border-color: var(--positive-general); color: var(--positive-general) } .toast-icon.error { border-color: var(--negative-general); color: var(--negative-general); } .custom-toast-cross { padding: 10px; font-size: 30px; color: var(--dark-text); cursor: pointer; transition: transform 0.15s, color 0.15s; display: flex; align-items: center; } .custom-toast-cross:hover { color: var(--text-selected); transform: scale(1.1); } .bar-space { width: 100%; display: flex; flex-direction: row; align-items: center; padding: 0 5px; } .driver1-number { font-size: 18px; margin-right: 10px; transition: color 0.15s; width: 12.5%; text-align: right; } .driver2-number { font-size: 18px; margin-left: 10px; width: 12.5%; transition: color 0.15s; } .driver1-number.little, .driver2-number.little { font-size: 13px; height: 27px; padding-top: 5px; } .avg-comparison { display: flex; height: 27px; gap: 70px; } .driver1-avg, .driver2-avg { font-size: 18px; } .avg-separator { width: 2px; height: 20px; margin-top: 3px; background-color: var(--text-general); } .toast-error { color: var(--negative-general) !important; } .hide { animation: fadeOut 0.15s; } .myShow { animation: myFadeIn 0.15s; } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } @keyframes myFadeIn { from { opacity: 0; } to { opacity: 1; } } .tools-panel { color: var(--white-general); background-color: transparent; display: flex; flex-direction: row; position: relative; align-items: center; border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .patreon-login { padding-right: 15px; } body:has(#blockDiv:not(.disappear)) .patreon-login .patreon-login-button, body:has(#blockDiv:not(.disappear)) .patreon-login #userToolButton { color: var(--text-muted); } body:has(#blockDiv:not(.disappear)) .patreon-login .patreon-login-button:hover, body:has(#blockDiv:not(.disappear)) .patreon-login #userToolButton:hover { color: var(--new-primary); } .patreon-login .patreon-login-button:hover { color: var(--new-primary); } .patreon-login .patreon-login-button:active { transform: scale(0.97); color: var(--new-primary); } body.og-theme .tools-panel { border-bottom: 1px solid var(--slight-contrast); } @keyframes move { 0% { left: -300px; } 100% { left: 100%; } } .mode-line { position: relative; width: 100%; height: 1px; background-color: var(--separator); } .mode-line.ai { background-color: var(--mode-line-ai); /* box-shadow: 0 0 6px 1px var(--mode-line-ai); */ } .mode-line.edit { background-color: var(--mode-line-edit); /* box-shadow: 0 0 6px 1px #fff7b0; */ } .mode-line.view { background-color: var(--mode-line-view); /* box-shadow: 0 0 6px 1px var(--mode-line-view); */ } .moving-line { position: absolute; width: 300px; height: 1px; background: transparent; animation: move 10s linear infinite; } .background { background-color: var(--new-primary); background-image: url('../assets/images/background2.svg'); background-size: cover; background-position: center; background-repeat: no-repeat; height: 100%; margin: 0; position: absolute; width: 100.2%; z-index: -5; left: -2px; opacity: 1; } body.light-theme .background { background-image: url('../assets/images/background2light.svg'); } body.og-theme .background { background-image: url('../assets/images/background2og.svg'); /* background-image: none; background-color: var(--background); */ } body.vaporwave-theme .background { background-image: url('../assets/images/background2vaporwave.svg'); } body.ferrari-theme .background { background-image: url('../assets/images/background2.svg'); filter: hue-rotate(-28deg) saturate(1.4) brightness(0.8); } body.redbull-theme .background { background-image: url('../assets/images/background2.svg'); filter: hue-rotate(145deg) saturate(1.3) brightness(0.8); } body.mercedes-theme .background { background-image: url('../assets/images/background2.svg'); filter: hue-rotate(175deg) saturate(1.25) brightness(0.8); } body.astonmartin-theme .background { background-image: url('../assets/images/background2.svg'); filter: hue-rotate(120deg) saturate(1.25) brightness(0.78); } body.mclaren-theme .background { background-image: url('../assets/images/background2.svg'); filter: hue-rotate(20deg) saturate(1.35) brightness(0.8); } .main-panel { display: flex; position: relative; height: 100%; overflow-x: hidden; } #season_viewer.script-view{ height: 91vh; } .script-view { display: flex; width: 100%; transition: opacity 0.15s; position: absolute; top: 0; left: 0; opacity: 1; height: 90vh; /* overflow-x: hidden; */ overflow-y: auto; } .script-view.hide { opacity: 0; pointer-events: none; visibility: none; } .script-view.hide.unloaded { display: none; opacity: 0; pointer-events: none; visibility: none; } .script-view.enter-from-right { animation: slide-in-from-right 0.15s ease-out forwards; } .script-view.enter-from-left { animation: slide-in-from-left 0.15s ease-out forwards; } .scirpt-info { font-size: 15px; } .script-selector { display: flex; flex-direction: column; align-items: flex-start; flex-grow: 1; margin-right: 20px; transition: transform 0.15s ease, opacity 0.15s ease; } .script-selector .nav-pills > li.nav-item { opacity: 1; transform: none; } .script-selector.hidden .nav-pills > li.nav-item { opacity: 0; transform: translateX(-12px); } @keyframes script-pill-reveal { 0% { opacity: 0; transform: translateX(-12px); } 65% { opacity: 1; transform: translateX(2px); } 100% { opacity: 1; transform: none; } } .script-selector:not(.hidden) .nav-pills > li.nav-item { animation: script-pill-reveal 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(1) { animation-delay: 0ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(2) { animation-delay: 45ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(3) { animation-delay: 90ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(4) { animation-delay: 135ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(5) { animation-delay: 180ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(6) { animation-delay: 225ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(7) { animation-delay: 270ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(8) { animation-delay: 315ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(9) { animation-delay: 360ms; } .script-selector:not(.hidden) .nav-pills > li.nav-item:nth-child(10) { animation-delay: 405ms; } @media (prefers-reduced-motion: reduce) { .script-selector .nav-pills > li.nav-item { animation: none; transform: none; } .script-selector.hidden .nav-pills > li.nav-item { transform: none; } } .script-selector.hidden, .gear-container.hidden, .footer.hidden { opacity: 0; pointer-events: none; } .general-config { display: flex; flex-direction: row; gap: 10px; } .gear-container { font-size: 16px; width: 0px; height: 40px; justify-content: center; align-items: center; position: relative; transition: width 0.15s, opacity 0.15s; cursor: pointer; border-top: 2px solid transparent; border-bottom: 2px solid transparent; } .gear-container::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--white-gradient), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; } .gear-container:hover::before { opacity: 1; } .gear-container:not(:hover)::before { transition-delay: 0s; } .gear-container:not(:has(.hidden)) { width: 40px; } .gear-container:hover .bi-gear-fill { opacity: 0; } .gear-container:hover .standard-line, .gear-container:hover { width: 117px; } .gear-container .standard-label { width: 160px; } .gear-container:hover .standard-label { opacity: 1; } .standard-label { opacity: 0; transition: opacity 0.10s; position: absolute; left: 8px; cursor: pointer; pointer-events: none; bottom: 7px; } .gear-container .bi-gear-fill { color: var(--text-general); opacity: 1; transition: color 0.15s, opacity 0.1s; left: 10px; font-size: 20px; position: absolute; bottom: 5px; } .gear-container .bi-gear-fill.hidden { opacity: 0; pointer-events: none; } .gear-container.bi-gear-fill:hover .standard-line { width: 100%; background-color: var(--text-general); } .save-selector { display: flex; flex-direction: column; transition: width 0.15s; } .custom-dropdown { max-width: 250px !important; min-width: 100px !important; justify-content: space-between; margin-top: 0.6rem !important; color: var(--white-general) !important; transition: color 0.15s !important; background-color: transparent !important; border: 0px !important; position: relative; padding: 6px 10px !important; display: flex !important; align-items: center; justify-content: space-around; } .custom-dropdown::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; z-index: -1; } #numberButton::before { z-index: 0 !important; } .custom-dropdown:hover::before { opacity: 1; } .custom-dropdown:not(:hover)::before { transition-delay: 0s; } .custom-dropdown+.dropdown-line { width: 0%; background-color: var(--new-primary); } .custom-dropdown:hover+.dropdown-line { width: 100%; background-color: var(--new-primary); } .custom-dropdown-no-margin { max-width: 250px !important; background-color: var(--separator) !important; margin-bottom: 1% !important; border: 2px solid transparent !important; border-radius: 10px !important; color: var(--white-general) !important; transition: border-color 0.15s !important; } .dropdown-line { height: 2px; width: 0%; background-color: var(--text-general); transition: width 0.15s, color 0.15s; } .standard-line { height: 2px; width: 0%; background-color: var(--text-general); transition: width 0.15s, color 0.15s; position: absolute; bottom: 0; left: -2px; } .new-custom-dropdown::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--white-gradient), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; } .new-custom-dropdown:hover::before { opacity: 1; } .new-custom-dropdown:not(:hover)::before { transition-delay: 0s; /* No delay when not hovering */ } #saveSelector { min-width: 140px; } #saveSelector:focus::before { opacity: 1; } #saveSelector:focus+.dropdown-line { width: 100%; } .new-custom-dropdown { max-width: 250px !important; background-color: transparent !important; color: var(--white-general) !important; border: 0px !important; position: relative; padding: 6px 10px !important; display: flex; align-items: center; justify-content: space-around; } .new-custom-dropdown:hover+.dropdown-line { width: 100%; background-color: var(--text-general); } #numberButton { flex-direction: row; align-items: center; margin-top: 0 !important; font-size: 20px; } .number-details .form-check { font-size: 18px; } .custom-dropdown-no-margin:hover { border: 2px solid var(--white-general) !important; } .custom-dropdown-no-margin:active { background-color: var(--active-general) !important; } .addTracks-button { margin-top: 0.4rem; max-width: 210px; background-color: var(--separator) !important; margin-bottom: 1%; border: 2px solid transparent !important; border-radius: 10px !important; box-shadow: none; } .addTracks-button:hover { border: 2px solid var(--white-general) !important; } .addTracks-button:active { background-color: var(--active-general); } #addTrackMenu { max-height: 300px; overflow-y: scroll; } #addTrackMenu::-webkit-scrollbar { width: 4px; } #addTrackMenu::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px; } #addTrackMenu::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } .nav-item { cursor: pointer; } .menu-race { max-width: 200px; } .name-flag { margin-left: 8px; width: 30px; height: 24px; border-radius: 20px; opacity: 0.9; } .name-div-edit-stats { display: flex; align-items: center; gap: 4px; /* max-height: 24px; */ } .menuFlag { height: 10px; width: 20px; margin-left: 10px; } #edit_teams { display: flex; flex-direction: column; } #objAndYear { display: flex; flex-direction: row; gap: 15px; align-items: center; } body.og-theme .main-columns-drag-section, body.og-theme .main-editStats-section, body.og-theme .main-viewer-section, body.og-theme .main-calendar-section { background-color: var(--background); border: 1px solid var(--slight-contrast); box-shadow: none; } .main-columns-drag-section { margin-left: 14px; margin-right: 14px; background-color: var(--generals); color: var(--white-general); overflow-y: auto; border-radius: 10px; box-shadow: var(--shadow-s) } .attributes-panel { display: flex; flex-direction: row; gap: 20px; } .attributes-column { display: flex; flex-direction: column; gap: 10px; width: 100%; } .stats-graph { width: 30%; padding: 0px; height: 230px; transform: translateY(53px); border-left: 3px solid var(--separator-light); } .title-and-stats { display: flex; flex-direction: column; gap: 10px; width: 100%; padding: 18px 0px 10px 25px; } .stats-and-graph { display: flex; flex-direction: row; gap: 20px; width: 100%; } .attributes-title { font-size: 20px; color: var(--text-secondary); } .main-panel-stats, .other-attributes-stats { display: flex; flex-direction: column; width: 100%; gap: 10px; } .stats-row { display: flex; flex-direction: row; gap: 15px; align-items: center; } .stats-row-separator { width: 2px; background-color: var(--separator-light); height: 63px; } .stats-row-separator-invisible { width: 2px; background-color: transparent; height: 60px; pointer-events: none; opacity: 0; } .extra-atributes { display: grid; width: 100%; grid-template-columns: repeat(3, 0.34fr); gap: 10px 15px; } .bottom-panel { display: flex; flex-direction: row; } .left-panel-stats { width: 100%; } .custom-hr { border: none; height: 2px; /* border: 1px solid var(--separator); */ margin-left: 10px; margin-right: 10px; margin-top: 1rem; margin-bottom: 1rem; background-color: var(--separator); } .categoryPills, .scriptPills, .generalPills { padding: 6px 12px !important; border-radius: 10px !important; } .pill-types { display: flex; flex-direction: row; color: var(--text-general); font-size: 18px; width: 100%; } .pill-types .activeType { color: var(--white-general); } #editPills { position: absolute; z-index: 1; right: 350px; transition: color 0.15s; } #dataPills { z-index: 1 !important; padding-left: 144px; transition: color 0.15s; } #iaPills { z-index: 1 !important; padding-left: 36px; transition: color 0.15s; } #iaPills .pill-line { background-color: var(--mode-line-ai); } #dataPills .pill-line { background-color: var(--mode-line-view); } #editPills .pill-line { background-color: var(--mode-line-edit); } #predict_results, #edit_stats, #car_performance, #driver_transfers, #regulations, #season_viewer, #head2head_viewer, #car_performance, #season_mods, #news { flex-direction: column; } #regulations .main-viewer-section.points-and-cfd{ margin-left: 0px; flex-grow: 1; } #regulations .custom-input-number{ padding: 0 3px; } #regulations .small-regulations .custom-input-number{ max-width: 25px; } #regulations .main-viewer-section.small-regulations{ width: 36%; } #regulations .main-viewer-section.small-regulations .viewer-element{ height: 56px; } #regulations .regulations-main { flex: 1; min-height: 0; gap: 14px; max-height: calc(100% - 110px); } #regulations .regulations-panels { display: flex; gap: 14px; min-height: 0; } #regulations .regulations-panels .viewer-details { min-height: 0; flex: 1 1 0; min-width: 0; } #regulations .viewer-details { padding: 14px; display: flex; flex-direction: column; gap: 12px; } #regulations .viewer-title { font-family: 'Formula1Bold'; font-size: 18px; color: var(--white-general); } #regulations .viewer-title-row { display: flex; align-items: center; gap: 12px; min-width: 0; } #regulations .viewer-element { background-color: var(--elements); border-radius: 10px; padding: 10px 12px; box-shadow: var(--shadow-s); display: flex; align-items: center; justify-content: space-between; gap: 12px; } #regulations .viewer-element-name { color: var(--text-secondary); font-size: 16px; } #regulations .viewer-element .stat-number { gap: 6px; margin: 0px !important; } #regulations .regulations-table-wrap { overflow-x: auto; } #regulations .regulations-grid { width: 100%; display: flex; flex-direction: column; gap: 2px; } .regulations-grid-resources .regulations-grid-header, .regulations-grid-resources .regulations-row, .regulations-grid-points .regulations-grid-header, .regulations-grid-points .regulations-row { justify-content: space-between; } #regulations .regulations-grid-header { display: flex; gap: 8px; padding: 0 10px 6px; } #regulations .regulations-grid-body { display: flex; flex-direction: column; gap: 6px; } #regulations .regulations-row { display: flex; gap: 8px; align-items: center; background-color: color-mix(in srgb, var(--background) 55%, transparent); border-radius: 8px; padding: 8px 10px; } #regulations .regulations-cell { color: var(--text-general); } #regulations .regulations-colheader { font-family: 'Formula1Bold'; font-size: 12px; text-transform: uppercase; color: var(--text-secondary); white-space: nowrap; } #regulations .regulations-keycell { font-family: 'Formula1Bold'; color: var(--white-general); white-space: nowrap; text-align: center; width: 45px; text-align: center; cursor: default; } #regulations #regPointSchemeBody input.custom-input-number, #regulations #regResourcePackageBody input.custom-input-number, .regulations-grid-resources .regulations-grid-header .regulations-colheader, .regulations-grid-points .regulations-grid-header .regulations-colheader { width: 45px; text-align: center; } .wind-tunnel-header, .points-header { width: min-content !important; } #regulations #regSpendingCap.custom-input-number { max-width: 155px; } #regulations .regulations-switch.form-switch { padding-left: 0; display: flex; align-items: center; } .form-check{ margin-bottom: 0px !important; } #regulations .regulations-switch .form-check-input { margin: 0; } #probText { padding-top: 2px; color: var(--text-general); } #probSelector { display: flex; align-items: center; } #raceProbButton, #yearPredictionModalButton { margin-top: 0.45rem !important; } #probViewer { width: 100%; height: 660px; max-height: 660px; } #mainProb { width: 100%; } /* DRIVERS TABLE */ .first { color: var(--table-first) !important; } .teams-table-row .teams-table-team { width: 9.4%; gap: 2px; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; } .teams-table-row .teams-table-team span { line-height: 1.03; } .teams-table-engine-name { color: var(--engine-table-name); display: block; text-transform: uppercase; font-size: 13px; } .teams-table-engine { display: flex; align-items: flex-end; gap: 3px; } .teams-table-engine-logo { width: 12px; height: 12px; object-fit: contain; flex: 0 0 auto; } .teams-table-row:hover .teams-table-engine-name { color: var(--engine-table-name-hover); } .drivers-table-row.odd:hover .first, .drivers-table-row:hover .first, .teams-table-row.odd:hover .first, .teams-table-row:hover .first { background-color: var(--table-first) !important; color: var(--table-hover-text) !important; } .second { color: var(--table-second) !important; } .drivers-table-row.odd:hover .second, .drivers-table-row:hover .second, .teams-table-row.odd:hover .second, .teams-table-row:hover .second { background-color: var(--table-second) !important; color: var(--table-hover-text) !important; } .third { color: var(--table-third) !important; } .drivers-table-row.odd:hover .third, .drivers-table-row:hover .third, .teams-table-row.odd:hover .third, .teams-table-row:hover .third { background-color: var(--table-third) !important; color: var(--table-hover-text) !important; } .fastest { text-decoration: underline; text-decoration-color: #c90fd7; text-decoration-thickness: 2px; text-underline-offset: 2px; } .dotd::after { content: ""; position: absolute; top: 0; right: 0; width: 12%; height: 27%; border-top: 3px solid var(--positive-general); border-right: 3px solid var(--positive-general); } .dotd::before { content: ""; position: absolute; bottom: 0; left: 0; width: 12%; height: 27%; border-bottom: 3px solid var(--positive-general); border-left: 3px solid var(--positive-general); } /* .drivers-table-row:hover .dotd::before { border-bottom: 3px solid var(--table-hover-text); border-left: 3px solid var(--table-hover-text); } .drivers-table-row:hover .dotd::after { border-top: 3px solid var(--table-hover-text); border-right: 3px solid var(--table-hover-text); } */ .drivers-table-header, .teams-table-header { display: flex; flex-direction: row; color: var(--text-general); padding-top: 1px; background-color: var(--table-header); box-shadow: var(--shadow-s); border-top-right-radius: 10px; border-top-left-radius: 10px; } .drivers-table-row { display: flex; flex-direction: row; height: 30px; } .teams-table-row { display: flex; flex-direction: row; height: 60px; } .drivers-table-data, .teams-table-data { overflow-y: auto; font-size: 15px; max-height: 80vh; } :not(.f2-table-data) .drivers-table-row:first-child .drivers-table-position, :not(.f2-table-data) .teams-table-row:first-child .teams-table-position { background-color: var(--f1-first); color: var(--text-general); } .f2-table-data .drivers-table-row:first-child .drivers-table-position, .f2-table-data .teams-table-row:first-child .teams-table-position { background-color: var(--f2-back); color: var(--text-general); } .f3-table-data .drivers-table-row:first-child .drivers-table-position, .f3-table-data .teams-table-row:first-child .teams-table-position { background-color: var(--f3-back); color: var(--text-general); } .drivers-table-row:has(.drivers-table-position.champion) .drivers-table-position, .teams-table-row:has(.teams-table-position.champion) .teams-table-position { border-left: 2px solid #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, var(--table-default-odd) 75%); } .drivers-table-row:has(.drivers-table-position.champion):hover .drivers-table-position, .teams-table-row:has(.teams-table-position.champion):hover .teams-table-position { border-left: 2px solid #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, var(--text-general) 75%); color: var(--negative-text) !important; } .drivers-table-row:has(.drivers-table-position.champion) .drivers-table-points, .teams-table-row:has(.teams-table-position.champion) .teams-table-points { border-right: 2px solid #FEDB37; background: linear-gradient(270deg, rgba(254, 219, 55, 0.35) 0%, var(--table-default-odd) 75%); } .drivers-table-row:has(.drivers-table-position.champion):hover .drivers-table-points, .teams-table-row:has(.teams-table-position.champion):hover .teams-table-points { border-right: 2px solid #FEDB37; background: linear-gradient(270deg, rgba(254, 219, 55, 0.35) 0%, var(--text-general) 75%); color: var(--negative-text) !important; } .drivers-table-data::-webkit-scrollbar { width: 4px !important; margin-left: 4px; } .drivers-table-data::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px !important; } .drivers-table-data::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } .drivers-table-row:hover .drivers-table-driver, .drivers-table-row:hover .drivers-table-normal, .teams-table-row:hover .teams-table-normal { background-color: var(--text-general); color: var(--negative-text); } .drivers-table-row.odd:hover .drivers-table-driver, .drivers-table-row.odd:hover .drivers-table-normal, .teams-table-row.odd:hover .teams-table-normal { background-color: var(--text-general); color: var(--negative-text); } .drivers-table-row:hover .drivers-table-position, .teams-table-row:hover .teams-table-position, .drivers-table-row-odd:hover .drivers-table-position, .teams-table-row-odd:hover .teams-table-position, .drivers-table-row.odd:hover .standings-pos-change, .teams-table-row.odd:hover .standings-pos-change, .drivers-table-row.odd:hover .standings-points-gap, .teams-table-row.odd:hover .standings-points-gap { background-color: var(--text-general) !important; color: var(--negative-text) !important; } .drivers-table-row:hover .drivers-table-points, .teams-table-row:hover .teams-table-points, .drivers-table-row:hover .standings-pos-change, .teams-table-row:hover .standings-pos-change, .drivers-table-row:hover .standings-points-gap, .teams-table-row:hover .standings-points-gap{ background-color: var(--text-general) !important; color: var(--negative-text); } .drivers-table-row:hover .standings-pos-change.up, .teams-table-row:hover .standings-pos-change.up { color: var(--positive-general-darker) !important; } .drivers-table-row:hover .standings-pos-change.down, .teams-table-row:hover .standings-pos-change.down { color: var(--negative-general) !important; } .drivers-table-row:hover, .teams-table-row:hover { cursor: pointer; } .drivers-table-row span.bold-font { color: var(--white-general); } .drivers-table-row:hover span.bold-font { color: var(--negative-text) !important; } .drivers-table-row .drivers-table-normal, .teams-table-row .teams-table-normal, .drivers-table-row .drivers-table-driver, .drivers-table-row .drivers-table-points, .drivers-table-row .drivers-table-position, .drivers-table-row .drivers-table-logo-div, .teams-table-row .standings-pos-change, .teams-table-row .standings-points-gap, .drivers-table-row .standings-pos-change, .drivers-table-row .standings-points-gap { background-color: var(--table-default); } .drivers-table-row.odd .drivers-table-normal, .teams-table-row.odd .teams-table-normal, .drivers-table-row.odd .drivers-table-driver, .drivers-table-row.odd .drivers-table-points, .drivers-table-row.odd .drivers-table-position, .drivers-table-row.odd .drivers-table-logo-div, .drivers-table-row.odd .standings-pos-change, .teams-table-row.odd .standings-pos-change, .teams-table-row.odd .standings-points-gap, .drivers-table-row.odd .standings-points-gap { background-color: var(--table-default-odd); } .teams-table-row .teams-table-team, .teams-table-row .teams-table-points, .teams-table-row .teams-table-position { background-color: var(--table-default) } .teams-table-row .teams-table-team { padding: 0 7px; } .teams-table-row.odd .teams-table-team, .teams-table-row.odd .teams-table-points, .teams-table-row.odd .teams-table-position { background-color: var(--table-default-odd); } .teams-table-row:hover .teams-table-team { background-color: var(--text-general); color: var(--negative-text); border-color: var(--text-general); } .drivers-table-normal[data-pos="DNF"] { color: var(--table-dnf); } .drivers-table-normal, .teams-table-normal { flex: 1 0 0; text-align: center; display: flex; justify-content: center; align-items: center; font-size: 14px; position: relative; } .teams-table-multi { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; gap: 2px 6px; width: 100%; line-height: 1.1; } .teams-table-multi-item { white-space: nowrap; } .f3-table-data .teams-table-multi-item { font-size: 12px; } .drivers-table-position, .teams-table-position { width: 40px; display: flex; align-items: center; justify-content: center; padding-top: 3px; position: relative; } .drivers-table-data .drivers-table-points, .teams-table-data .teams-table-points { padding-top: 3px; } #season_viewer { --standings-details-col-width: 0px; } #season_viewer.standings-details-enabled { --standings-details-col-width: 44px; } .drivers-table-header .standings-pos-change, .teams-table-header .standings-pos-change, .drivers-table-header .standings-points-gap, .teams-table-header .standings-points-gap { font-family: "Formula1Bold" !important; padding-top: 0px !important; } .standings-pos-change, .standings-points-gap { flex: 0 0 var(--standings-details-col-width); width: var(--standings-details-col-width); min-width: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; gap: 1px; font-size: 13px; line-height: 1; font-family: 'NumbersFont'; padding-top: 0; } #season_viewer.standings-details-enabled .standings-pos-change, #season_viewer.standings-details-enabled .standings-points-gap { padding-top: 3px; } .standings-pos-change.up { color: var(--positive-general); } .standings-pos-change.down { color: var(--negative-general); } .standings-pos-change.neutral { color: var(--text-secondary); } .standings-points-gap { color: var(--text-secondary); } .drivers-table-row:has(.drivers-table-points.eliminated) .standings-points-gap, .teams-table-row:has(.teams-table-points.eliminated) .standings-points-gap { color: var(--table-dnf) !important; } #season_viewer.standings-details-enabled .drivers-table-row.last-title-contender { border-bottom: 1px solid var(--table-dnf); } #season_viewer.standings-details-enabled .teams-table-row.last-title-contender { border-bottom: 1px solid var(--table-dnf); } .drivers-table-data .drivers-table-position, .teams-table-data .teams-table-position { font-family: 'NumbersBold' !important; font-size: 16px; } .drivers-table-data .drivers-table-points, .teams-table-data .teams-table-points { font-family: 'NumbersBold' !important; font-size: 16px; } .drivers-table-header .drivers-table-position, .teams-table-header .teams-table-position { padding-top: 0; padding-bottom: 1px; } .aston-team-table-logo { padding: 6px 0 !important; } .racingpoint-team-table-logo, .jordan-team-table-logo { padding: 9px 4px !important; } .cadillac-team-table-logo { padding: 9px 5px !important; } .ferrari-team-table-logo { padding: 8px !important; } .mclaren-team-table-logo { margin-right: 3px !important; } .teams-table-logo-inner.mclaren-team-table-logo:not(img) { -webkit-mask: url('/assets/images/logos/mclaren.svg') no-repeat center; mask: url('/assets/images/logos/mclaren.svg') no-repeat center; -webkit-mask-size: 90%; mask-size: 90%; background-color: #f0f0f0; } .redbull-team-table-logo { padding: 13px 0px !important; } .ford-team-table-logo { padding: 10px 4px !important; } .merc-team-table-logo { padding: 9px !important; } .alpine-team-table-logo { padding: 13px 2px !important } .williams-team-table-logo { padding: 2px!important; } .haas-team-table-logo { padding: 7px !important; } .stake-team-table-logo { padding: 3px !important; } .alphatauri-team-table-logo { padding: 15px 9px !important; } .hugo-team-table-logo { padding: 5px 1px !important; } .toyota-team-table-logo { padding: 13px 8px !important; } .teams-table-logo-inner.toyota-team-table-logo:not(img) { -webkit-mask: url('/assets/images/logos/toyota.svg') no-repeat center; mask: url('/assets/images/logos/toyota.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--white-general); width: 48px; } .porsche-team-table-logo { padding: 11px !important; } .brawn-team-table-logo { padding: 24px 6px !important; } .audi-team-table-logo { padding: 20px 5px !important; } .custom-team-table-logo { padding: 1px !important; } .drivers-table-logo { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; opacity: 1; transition: opacity 0.15s; } .teams-table-logo { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; transition: border-height 0.15s; position: relative; z-index: 1; } .teams-table-logo-inner { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; padding: 5px; position: relative; z-index: 1; } .junior-team-logo-driver{ width: 30px; height: 30px; } .junior-team-logo-team{ width: 60px; } .team-logo-abbr { font-family: 'Formula1Bold'; font-size: 12px; letter-spacing: 0.5px; color: #f5f5f5; text-transform: uppercase; width: 30px; height: 100%; display: flex; align-items: center; justify-content: center; } /* phm */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="17"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="31"]) img{ padding: 7px 0; width: 32px; } /* mp */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="16"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="27"]) img { width: 38px; padding: 3px 0px; } .junior-formula-logo img[data-junior-team-id="16"], .junior-formula-logo img[data-junior-team-id="27"] { width: 38px; } /* carlin*/ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="13"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="29"]) img { padding: 0px; } .junior-formula-logo img[data-junior-team-id="13"], .junior-formula-logo img[data-junior-team-id="29"] { padding: 0px } .team-logo-abbr:has(.junior-team-logo-team[data-teamid="13"]) img, .team-logo-abbr:has(.junior-team-logo-team[data-teamid="29"]) img { width: 55px; } /*trident*/ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="21"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="23"]) img { width: 45px; } .junior-formula-logo img[data-junior-team-id="21"], .junior-formula-logo img[data-junior-team-id="23"] { width: 40px; } /* art */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="15"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="24"]) img { width: 38px; } .junior-formula-logo img[data-junior-team-id="15"], .junior-formula-logo img[data-junior-team-id="24"] { width: 34px; } /*hitech*/ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="14"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="25"]) img { width: 38px; } .junior-formula-logo img[data-junior-team-id="14"], .junior-formula-logo img[data-junior-team-id="25"] { width: 34px; } .team-logo-abbr:has(.junior-team-logo-team[data-teamid="14"]) img, .team-logo-abbr:has(.junior-team-logo-team[data-teamid="25"]) img { width: 74px; } /* var */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="20"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="26"]) img { width: 38px; } .junior-formula-logo img[data-junior-team-id="20"], .junior-formula-logo img[data-junior-team-id="26"] { width: 32px; } /* prema */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="11"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="22"]) img { padding: 6px 2px; } .junior-formula-logo img[data-junior-team-id="11"], .junior-formula-logo img[data-junior-team-id="22"] { padding: 3px 1px; } /* campos */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="19"]) img, .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="28"]) img { width: 38px; } .junior-formula-logo img[data-junior-team-id="19"], .junior-formula-logo img[data-junior-team-id="28"] { width: 30px; } /* invicta */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="12"]) img { width: 42px; } .junior-formula-logo img[data-junior-team-id="12"] { width: 32px; } /* dams */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="18"]) img { width: 36px; } /* jenzer */ .team-logo-abbr:has(.junior-team-logo-driver[data-teamid="30"]) img { width: 38px; } .driver-space:not(.affiliates-space) .junior-formula-logo{ display: none; } .junior-formula-logo { width: 26px; height: 22px; display: flex; justify-content: center; } .junior-formula-logo img{ width: 26px; height: 22px; } .junior-formula-logo.f2-team{ padding-left: 2px; border-left: 2px solid var(--f2-back); } .junior-formula-logo.f3-team{ padding-left: 4px; border-left: 2px solid var(--f3-back); } .team-logo-abbr-small { font-size: 11px; } .team-logo-abbr-large { font-size: 14px; } .drivers-table-logo { padding: 0px 2px; } .logo-up-down, .logo-lotus-table { padding: 5px 0; } .logo-up-down-little { padding: 2px 1px; } .logo-up-down-mid { padding: 6px 0px; } .logo-up-down-extra { padding-top: 10px; padding-bottom: 10px; } .bento-grid .logo-up-down-extra, .session-results-table .logo-up-down-extra { padding-top: 8px; padding-bottom: 8px; } .records-list .logo-up-down-extra { padding-top: 12px; padding-bottom: 12px; } .bento-grid .logo-ferrari-table{ padding: 4px; } .logo-ferrari-table{ padding: 5px; } .logo-andretti-table, .logo-renault-table { padding: 4px 3px; } .logo-bmw-table{ padding: 2px 0 0 0; } .bmw-team-table-logo { padding: 4px 3px 1px 3px !important; } .logo-porsche-table { padding: 5px; } .bento-grid .logo-porsche-table{ padding: 4px; } .bento-grid .logo-sauber-table, .session-results-row .logo-sauber-table { width: 16px !important; } .bento-grid .logo-toyota-table{ width: 20px !important; } .session-results-row .logo-toyota-table { width: 22px !important; } .session-results-row .logo-ferrari-table{ padding: 4px !important; } .bento-grid .logo-cadillac-table, .session-results-row .logo-cadillac-table { padding: 1px 0px; } .logo-ford-table, .logo-jordan-table, .logo-cadillac-table { padding: 4px 2px; } .logo-racingpoint-table{ padding: 4px; } .logo-williams-table { padding: 6px 4px; } .logo-toyota-table{ width: 24px; } .logo-sauber-table{ height: 20px; } .drivers-table-row .hahoverback, .drivers-table-row .afhoverback{ width: 30px !important; height: 30px; } .drivers-table-logo-div:has(.logo-sauber-table), .drivers-table-logo-div:has(.logo-toyota-table){ display: flex; align-items: center; justify-content: center; width: 24px; } .logo-merc-table { padding: 4px; } .wihoverback { width: 30px; height: 30px; } .wihoverback:has(.logo-williams-2026-table) { padding: 0px 0px; } .athoverback { width: 30px; height: 30px; } .athoverback:has(.logo-visarb-table) { padding: 5px 3px; } .athoverback:has(.logo-hugo-table) { padding: 2px 0px; } .athoverback:has(.logo-alphatauri-table) { padding: 8px 4px; } .athoverback:has(.logo-brawn-table) { padding: 12px 0px; } .logo-visarb-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/visarb.svg') no-repeat center; mask: url('/assets/images/logos/visarb.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-hugo-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/hugoboss.svg') no-repeat center; mask: url('/assets/images/logos/hugoboss.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-alphatauri-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/alphatauri.svg') no-repeat center; mask: url('/assets/images/logos/alphatauri.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-brawn-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/brawn.svg') no-repeat center; mask: url('/assets/images/logos/brawn.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-alpine-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/alpine.svg') no-repeat center; mask: url('/assets/images/logos/alpine.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-mclaren-table { -webkit-mask: url('/assets/images/logos/mclaren.svg') no-repeat center; mask: url('/assets/images/logos/mclaren.svg') no-repeat center; -webkit-mask-size: 75%; mask-size: 75%; background-color: var(--mclaren-primary); } .logo-williams-2026-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--williams-primary); } .logo-toyota-table { -webkit-mask: url('/assets/images/logos/toyota.svg') no-repeat center; mask: url('/assets/images/logos/toyota.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--toyota-primary); } .logo-sauber-table { -webkit-mask: url('/assets/images/logos/sauber.svg') no-repeat center; mask: url('/assets/images/logos/sauber.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--sauber-primary); } .alhoverback { width: 30px; height: 30px; } .alhoverback:has(.logo-alpine-table) { padding: 6px 2px; } .alhoverback:has(.logo-andretti-table), .alhoverback:has(.logo-renault-table) { padding: 4px 3px } .alhoverback:has(.logo-lotus-table) { padding: 5px 0px; } .logo-andretti-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/andretti.svg') no-repeat center; mask: url('/assets/images/logos/andretti.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-renault-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/renault.svg') no-repeat center; mask: url('/assets/images/logos/renault.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .logo-lotus-table { width: 100%; height: 100%; -webkit-mask: url('/assets/images/logos/lotus.svg') no-repeat center; mask: url('/assets/images/logos/lotus.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .drivers-table-row:hover .logo-alpine-table, .drivers-table-row:hover .logo-mclaren-table, .drivers-table-row:hover .logo-visarb-table, .drivers-table-row:hover .logo-alphatauri-table, .drivers-table-row:hover .logo-williams-2026-table, .drivers-table-row:hover .logo-toyota-table, .drivers-table-row:hover .logo-sauber-table { background-color: #f0f0f0; } .drivers-table-row:hover .logo-renault-table, .drivers-table-row:hover .logo-lotus-table, .drivers-table-row:hover .logo-andretti-table, .drivers-table-row:hover .logo-lotus-table, .drivers-table-row:hover .logo-brawn-table { background-color: #333333; } .logo-reduce { padding: 4px; } .logo-stake-table { padding: 2px 0px; } .drivers-table-header .drivers-table-position, .teams-table-header .teams-table-position { border-top-left-radius: 5px; } .drivers-table-driver, .teams-table-team { width: calc(9.4% + 60px); display: flex; align-items: center; padding-left: 4px; } .drivers-table-row .drivers-table-driver { width: calc(9.4% + 30px); border-right: 0px solid transparent; gap: 3px; overflow: hidden; } .drivers-table-points, .teams-table-points { width: 80px; display: flex; justify-content: center; align-items: center; } .drivers-table-header .drivers-table-points, .teams-table-header .teams-table-points { border-top-right-radius: 5px; } .drivers-table-row:hover .fehoverback { background-color: var(--ferrari-primary) } .teams-table-row .feiconback { background-color: var(--ferrari-primary) } .feNewBackground { background-color: var(--ferrari-primary); } .drivers-table-row:hover .ashoverback { background-color: var(--aston-primary) } .teams-table-row .asiconback { background-color: var(--aston-primary) } .asNewBackground { background-color: var(--aston-primary); } .drivers-table-row:hover .mehoverback { background-color: var(--mercedes-primary) } .teams-table-row .meiconback { background-color: var(--mercedes-primary) } .meNewBackground { background-color: var(--mercedes-primary); } .drivers-table-row:hover .rbhoverback { background-color: var(--redbull-primary) } .teams-table-row .rbiconback { background-color: var(--redbull-primary) } .rbNewBackground { background-color: var(--redbull-primary); } .drivers-table-row:hover .mchoverback { background-color: var(--mclaren-primary) } .teams-table-row .mciconback { background-color: var(--mclaren-primary) } .mcNewBackground { background-color: var(--mclaren-primary); } .drivers-table-row:hover .alhoverback { background-color: var(--alpine-primary) } .teams-table-row .aliconback { background-color: var(--alpine-primary) } .alNewBackground { background-color: var(--alpine-primary); } .drivers-table-row:hover .hahoverback { background-color: var(--haas-primary) } .teams-table-row .haiconback { background-color: var(--haas-primary) } .haNewBackground { background-color: var(--haas-primary); } .drivers-table-row:hover .wihoverback { background-color: var(--williams-primary) } .teams-table-row .wiiconback { background-color: var(--williams-primary) } .wiNewBackground { background-color: var(--williams-primary); } .drivers-table-row:hover .athoverback { background-color: var(--alphatauri-primary) } .teams-table-row .aticonback { background-color: var(--alphatauri-primary) } .athNewBackground { background-color: var(--alphatauri-primary); } .drivers-table-row:hover .afhoverback { background-color: var(--alfa-primary) } .teams-table-row .aficonback { background-color: var(--alfa-primary) } .afNewBackground { background-color: var(--alfa-primary); } .teams-table-row .cticonback { background-color: var(--custom-team-primary) } .drivers-table-row:hover .cthoverback { background-color: var(--custom-team-primary) } .ctNewBackground { background-color: var(--custom-team-primary); } body.light-theme .drivers-table:has(.f2-table-data) .drivers-table-row .drivers-table-logo-div, body.light-theme .drivers-table:has(.f3-table-data) .drivers-table-row .drivers-table-logo-div { background-color: var(--white-general); } .drivers-table-row:hover .prehoverback { background-color: #ebebeb !important; } .teams-table-row .preiconback { background-color: #ebebeb; } .drivers-table-row:hover .virhoverback { background-color: #2a2a2a !important; } .teams-table-row .viriconback { background-color: #2a2a2a; } .drivers-table-row:hover .carhoverback { background-color: #1b6fe0 !important; } .teams-table-row .cariconback { background-color: #1b6fe0; } .drivers-table-row:hover .hithoverback { background-color: #9FB4C7 !important; } .teams-table-row .hiticonback { background-color: #9FB4C7; } .drivers-table-row:hover .arthoverback { background-color: #2b2d42 !important; } .teams-table-row .articonback { background-color: #2b2d42; } .drivers-table-row:hover .mphoverback { background-color: #FF9144 !important; } .teams-table-row .mpiconback { background-color: #FF9144; } .drivers-table-row:hover .phmhoverback { background-color: #793118 !important; } .teams-table-row .phmiconback { background-color: #793118; } .drivers-table-row:hover .damhoverback { background-color: #00C8FF !important; } .teams-table-row .damiconback { background-color: #00C8FF; } .drivers-table-row:hover .camhoverback { background-color: #707070 !important; } .teams-table-row .camiconback { background-color: #707070; } .drivers-table-row:hover .varhoverback { background-color: #FF5A00 !important; } .teams-table-row .variconback { background-color: #FF5A00; } .drivers-table-row:hover .trihoverback { background-color: #7B01FF !important; } .teams-table-row .triiconback { background-color: #7B01FF; } .drivers-table-row:hover .jenhoverback { background-color: #00C8FF !important; } .teams-table-row .jeniconback { background-color: #00C8FF; } .new-team-logo { max-width: 50%; align-self: center; max-height: 60%; } .new-team-name { text-transform: uppercase; font-size: 36px; } .new-drivers-names { font-size: 28px; } .new-vs { font-size: 22px; font-family: 'Formula1'; padding: 0px 4px 4px 4px; position: relative; } .new-team-text { align-self: center; display: flex; align-items: center; justify-content: center; flex-direction: column; line-height: 1; } .driver-comparison-overlay { display: flex; height: 100%; justify-content: center; } .prob-viewer-data { overflow-y: scroll; max-height: 621px; } .prob-viewer-header, .prob-viewer-row { display: flex; flex-direction: row; color: var(--text-general); } .prob-row-odd { background-color: var(--elements); } .prob-row-even { background-color: var(--superficials); } .negative-text:hover { color: var(--negative-text) !important; } .dark-text:hover { color: var(--dark-text) !important; } .prob-viewer-header .viewer-header-data, .prob-viewer-row .viewer-header-data { margin-left: 4px; z-index: 3; } .prob-viewer-header { border-bottom: 1px solid var(--white-general); } .viewer-header-driver { width: 210px; padding: 0px 7px; height: 34px; display: flex; align-items: center; transition: background-color 0.15s, color 0.15s, text-shadow 0.15s; z-index: 3; overflow: hidden; } .prob-viewer-row { transition: background-color 0.15s, color 0.15s; position: relative; } .prob-viewer-row:hover { background-color: var(--dropdown-hover); cursor: pointer; } .prob-viewer-row .viewer-header-driver span.bold-font { transition: background-color 0.15s, color 0.15s; margin-left: 4px; } .prob-viewer-row .viewer-header-driver span { transition: background-color 0.15s, color 0.05s; } .prob-viewer-row .viewer-header-driver span.bold-font { transition: color 0.15s; } .prob-viewer-row .viewer-header-position { transition: background-color 0.15s, color 0.15s; padding-top: 4px; text-align: center; } .viewer-header-position { width: 50px; text-align: center; padding-top: 4px; z-index: 3; transition: text-shadow 0.15s; } .viewer-header-pos, .viewer-header-data { flex: 1 0 0; text-align: center; /* background-color: wheat; */ } .viewer-header-pos { padding-top: 4px; padding-left: 4px; } .viewer-header-data { border-radius: 5px; color: var(--negative-text); transition: border 0.15s; display: flex; align-items: center; justify-content: center; height: 28px; margin-top: 3px; } .viewer-header-data.i75-100 { background-color: var(--positive-general); transition: background-color 0.15s, color 0.15s, text-shadow 0.15s; } .prob-viewer-row:hover .viewer-header-data.i75-100 { background-color: transparent; color: var(--positive-general); text-shadow: 1px 1px 0 black; } .viewer-header-data.i50-75 { background-color: #7adfac; transition: background-color 0.15s, color 0.15s, text-shadow 0.15s; } .prob-viewer-row:hover .viewer-header-data.i50-75 { background-color: transparent; color: #7adfac; text-shadow: 1px 1px 0 black; } .viewer-header-data.i25-50 { background-color: #a2dfc0; transition: background-color 0.15s, color 0.15s, text-shadow 0.15s; } .prob-viewer-row:hover .viewer-header-data.i25-50 { background-color: transparent; color: #a2dfc0; text-shadow: 1px 1px 0 black; } .viewer-header-data.i0-25 { background-color: #c5e7d6; transition: background-color 0.15s, color 0.15s, text-shadow 0.15s; } .prob-viewer-row:hover .viewer-header-data.i0-25 { background-color: transparent; color: #c5e7d6; text-shadow: 1px 1px 0 black; } .save-selector-title { color: var(--text-general); transition: color 0.15s; } .save-selector-title.activeSelected { color: var(--white-general) !important; } .confirm-section { margin-left: 20px; margin-right: 20px; margin-bottom: 20px; margin-top: 15px; background-color: var(--tools-general); border: 1px solid var(--separator); border-radius: 10px; display: flex; justify-content: center; align-items: center; width: 100%; min-height: 276px; } .custom-confirm { width: 140px; border: 2.5px solid transparent !important; border-left: 0px !important; border-right: 0px !important; border-radius: 10px !important; color: var(--white-general) !important; transition: border-color 0.15s, background-color 0.15s, color 0.15s !important; background-color: transparent !important; } .custom-confirm::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; z-index: -1; } .custom-confirm:hover::before { opacity: 1; } .custom-confirm:not(:hover)::before { transition-delay: 0s; /* No delay when not hovering */ } .custom-confirm+.dropdown-line { width: 0%; background-color: var(--new-primary); } .custom-confirm:hover+.dropdown-line { width: 100%; } .extra-inputs { margin-bottom: 20px !important; margin-top: 20px !important; } .custom-delete { width: 140px; border: 2.5px solid transparent !important; border-left: 0px !important; border-right: 0px !important; border-radius: 10px !important; color: var(--white-general) !important; transition: border-color 0.15s, background-color 0.15s, color 0.15s !important; background-color: transparent !important; display: flex; justify-content: center; } .custom-delete::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--negative-general), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; z-index: -1; } .custom-delete:hover::before { opacity: 1; } .custom-delete:not(:hover)::before { transition-delay: 0s; /* No delay when not hovering */ } .custom-delete+.dropdown-line { width: 0%; background-color: var(--negative-general); } .custom-delete:hover+.dropdown-line { width: 100%; } .delete-mode:not(:hover) { background-color: transparent !important; color: var(--negative-general) !important; } .delete-mode:not(:hover) .dropdown-line { width: 0%; } .trash-and-text { display: flex; flex-direction: row; gap: 10px; align-items: center; font-size: 18px; } .trash-and-text:hover { cursor: pointer; } .main-viewer-section { margin-left: 14px; margin-right: 14px; background-color: var(--generals); color: var(--white-general); overflow: hidden; border-radius: 10px; box-shadow: var(--shadow-s); } .year-drivers-teams { padding-top: 0.6rem; padding-bottom: 0.7rem; position: absolute; display: flex; gap: 10px; } .prediction-buttons { padding-top: 0.6rem; padding-bottom: 0.7rem; position: absolute; display: flex; } .year-and-race { display: flex; flex-direction: row; gap: 10px; } .drivers-teams-pills { display: flex; align-items: flex-end; } .modal-buttons { gap: 10px; display: flex; } .team-table-logo-name { display: flex; align-items: center; gap: 7px; border-right: 4px solid transparent; transition: border 0.15s; width: 100%; height: 100%; } .flag-header { display: flex; justify-content: center; align-items: center; gap: 5px; } .flag-header img { width: 100%; height: 33px; } .text-in-front { position: absolute; z-index: 4; color: var(--text-in-front); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); font-size: 15px; background: linear-gradient(to right, var(--background), color-mix(in srgb, var(--background) 60%, transparent)); width: 100%; height: 100%; align-content: center; } .drivers-table:has(.f2-table-data) .sprint-label, .teams-table:has(.f2-table-data) .sprint-label { color: var(--f2-border); } .sprint-result-cell{ box-sizing: border-box; border-left: 1px solid var(--table-dnf); } .drivers-table:has(.f2-table-data) .standings-points-gap, .teams-table:has(.f2-table-data) .standings-points-gap, .drivers-table:has(.f3-table-data) .standings-points-gap, .teams-table:has(.f3-table-data) .standings-points-gap{ border-left: 1px solid var(--table-dnf); } .drivers-table:has(.f3-table-data) .sprint-label, .teams-table:has(.f3-table-data) .sprint-label { color: var(--f3-border); } .light-theme .drivers-table-header .flag-header img, .light-theme .teams-table-header .flag-header img { opacity: 0.85 !important; } .light-theme .text-in-front { background: transparent; } .main-calendar { width: 100%; margin-right: 14px; } .main-calendar-section { margin-left: 14px; color: var(--white-general); min-height: 660px; max-height: 660px; overflow-y: auto; border-radius: 10px; display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; overflow-x: hidden; grid-auto-rows: min-content; } .main-options-section { margin-left: 1.5rem; margin-right: 14px; border: 1px solid var(--separator); color: var(--white-general); min-height: 660px; max-height: 660px; overflow-y: auto; border-radius: 10px; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; } .save-calendar-section { height: 530px; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; } .buttons-h2h { display: flex; gap: 10px; } #driver1Button, #driver2Button { display: flex; flex-direction: row; align-items: center; } .race-calendar { position: relative; background-color: var(--elements); border-radius: 10px; padding: 8px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; display: flex; cursor: grab !important; max-height: 112px; gap: 8px; box-shadow: var(--shadow-s); justify-content: space-between; border: none; } .race-calendar.completed { cursor: default !important; } .race-calendar.drop-before { box-shadow: var(--shadow-s); } .race-calendar.drop-after { box-shadow: var(--shadow-s); } .race-series-badges { position: absolute; top: 6px; left: 6px; cursor: pointer; display: flex; gap: 4px; z-index: 2; } .race-series-badge { border: none; border-radius: 6px; padding: 2px 5px; font-size: 12px; background-color: var(--elements-hover); color: var(--text-secondary); cursor: default; box-shadow: var(--shadow-xs); transition: background-color 0.15s, color 0.15s; } .race-series-badge:hover { background-color: var(--table-hover); color: var(--text-general); } .race-series-badge:active { transform: scale(0.96); } .race-series-badge-f2.active-badge { color: var(--f2-back); background-color: color-mix(in srgb, var(--f2-back) 30%, transparent); } .race-series-badge-f2.active-badge:hover { color: var(--f2-border); background-color: color-mix(in srgb, var(--f2-border) 30%, transparent); } .race-series-badge-f3.active-badge { color: var(--f3-back); background-color: color-mix(in srgb, var(--f3-back) 30%, transparent); } .race-series-badge-f3.active-badge:hover { color: var(--f3-border); background-color: color-mix(in srgb, var(--f3-border) 30%, transparent); } .race-calendar-number { border-right: 2px solid var(--separator); font-size: 30px; display: flex; align-items: center; width: 20%; justify-content: center; padding-right: 12px; } .race-calendar-number-completed { color: var(--positive-general); font-size: 34px; } .race-calendar-number-delete { color: var(--delete-div); cursor: pointer; font-size: 28px; } .race-calendar-number-delete i { border-radius: 8px; transition: background-color 0.15s; padding: 0 4px; } .race-calendar-number-delete i:hover { background-color: color-mix(in srgb, var(--delete-div) 30%, transparent); } .upper-text-and-flag { display: flex; flex-direction: row; justify-content: center; position: relative; align-items: center; width: 95px; } .left-race { gap: 4px; display: flex; flex-direction: column; justify-content: space-between; } .right-race { display: flex; flex-direction: column; justify-content: space-between; gap: 2px; } .full-quali-weather { display: flex; flex-direction: row; gap: 4px; justify-content: space-between; } .session-name { font-size: 20px; } .weather-vis { width: 30px; height: 30px; border-radius: 10px; background-color: var(--superficials); border: none; font-size: 19px; text-align: center; } .weather-selector { display: flex; flex-direction: row; align-items: center; margin-left: 5px; font-size: 20px; padding-top: 2px; gap: 4px; } .weather-selector i { cursor: pointer; } .race-calendar:not(.completed):hover { color: var(--white-general); background-color: var(--elements-hover); } .race-calendar.completed, .race-calendar.completed:hover { color: var(--text-general); background-color: var(--elements); border: none; box-shadow: var(--shadow-s); } .race-calendar.completed .left-race, .race-calendar.completed .right-race, .race-calendar.completed .race-series-badges { pointer-events: none; } .pit-crew-button { width: 100px; } .pit-crew-group { display: flex; flex-direction: row; justify-content: space-between; gap: 10px; } .pit-crew-details { display: flex; flex-direction: column; color: var(--text-general); gap: 5px; } .pit-crew-stat { width: 23%; } .complete-div { position: absolute; top: -1px; left: -1px; width: 100.9%; height: 101.9%; background-color: var(--blur-color); z-index: 20; display: flex; justify-content: center; align-items: center; border-radius: 10px; color: var(--positive-general); transition: color 0.15s, border-color 0.15s; cursor: default; backdrop-filter: blur(5px); } .race-calendar.completed:hover { border-color: transparent; } .delete-div { position: absolute; top: -1px; left: -1px; width: 100.9%; height: 101.9%; background-color: var(--blur-color); z-index: 20; display: flex; justify-content: center; align-items: center; border-radius: 10px; color: var(--text-general); transition: color 0.15s, border-color 0.15s; cursor: default; backdrop-filter: blur(5px); border: none; } .delete-div:hover { color: var(--delete-div); } .upper-race { font-size: 24px; display: flex; flex-direction: row; gap: 10px; position: absolute; z-index: 2; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.76); color: var(--text-in-front); } .flag { width: 95px; height: 45px; border-radius: 6px; opacity: 0.45; } body.light-theme .flag, body.light-theme .flag-header img { opacity: 0.9; position: relative; } .main-editStats-section { background-color: var(--generals); color: var(--white-general); height: 660px; border-radius: 10px; scrollbar-width: thin; scrollbar-color: var(--white-general); box-shadow: var(--shadow-s); margin-right: 14px; } .staff-list::-webkit-scrollbar, .news-grid::-webkit-scrollbar, .records-list::-webkit-scrollbar, #juniorTeamContractMenu::-webkit-scrollbar{ width: 4px; } .staff-list::-webkit-scrollbar-thumb, .news-grid::-webkit-scrollbar-thumb, .records-list::-webkit-scrollbar-thumb, #juniorTeamContractMenu::-webkit-scrollbar-thumb{ background-color: var(--white-general); border-radius: 3px; } .staff-list::-webkit-scrollbar-thumb:hover, .news-grid::-webkit-scrollbar-thumb:hover, .records-list::-webkit-scrollbar-thumb:hover, #juniorTeamContractMenu::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } .staff-list { overflow-y: auto; height: 610px; margin-right: 4px; } .staff-logo { width: 27px; height: 26px; } .marquee-wrapper { position: relative; overflow: hidden; flex: 1; display: flex; } #free-staff .staff-logo { width: 32px; height: 23px; } #driverstats { margin-right: 14px; } .main-columns-drag-section::-webkit-scrollbar { width: 4px; } .main-columns-drag-section::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px; } .main-columns-drag-section::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } .categoryPills { color: #dbdbdb !important; } .categoryPills.active { background-color: var(--separator) !important; color: #fdfdfd !important; } #F2pill { border: 2px solid transparent; transition: border-color 0.15s, background-color 0.15s, color 0.15s; } #F2pill:hover { border: 2px solid var(--f2-border); background-color: var(--f2-back); color: var(--white-general) !important; } #F3pill { border: 2px solid transparent; transition: border-color 0.15s, background-color 0.15s, color 0.15s; } #F3pill:hover { border: 2px solid var(--f3-border); background-color: var(--f3-back); color: var(--white-general) !important; } #F2pill.active { background-color: var(--f2-active) !important; } #F3pill.active { background-color: red !important; } #mainH2h .col-3.main-viewer-section{ padding: 10px; } /*First .one-statH2H with margin top 25 px and no border top*/ #raceh2h { margin-top: 25px; } #dnfh2h { margin-bottom: 0px; } .one-statH2H { padding-top: 5px; display: flex; flex-direction: column; font-size: 20px; color: var(--text-general); transition: border-color 0.15s, background-color 0.15s; align-items: center; min-height: 66px; border-top: 2px solid var(--separator); padding: 10px 10px 0px; margin-bottom: 10px; } .one-statH2H:hover .name-H2H .only-name { color: var(--white-general); } .one-statH2H:hover .driver1-number, .one-statH2H:hover .driver2-number { color: var(--white-general); } .driver-names { display: flex; flex-direction: row; justify-content: space-around; font-size: 20px; height: 110px; margin-top: 6px; } .driver-space { min-width: 200px; max-width: 200px; min-height: 40px; } .staff-space { flex: 1 0 0; max-width: 20%; min-height: 40px; padding: 0 3px; } #free-staff .technical-chief-border { border-left: 2px solid var(--technical-chief); padding-left: 6px; } #free-staff .race-engineer-border { border-left: 2px solid var(--race-engineer); padding-left: 6px; } #free-staff .head-aero-border { border-left: 2px solid var(--head-aero); padding-left: 6px; } #free-staff .sporting-director-border { border-left: 2px solid var(--sporting-director); padding-left: 6px; } .staff-space[data-type="technical-chief"] { border-right: 1px solid var(--separator); border-left: 1px solid var(--separator); } .staff-space[data-type="sporting-director"], .staff-space[data-type="race-engineer"][data-pos="2"], .staff-space[data-type="head-aero"] { border-right: 1px solid var(--separator); } .affiliates-and-arrows { display: flex; flex-direction: row; align-items: center; gap: 2px; width: 60%; flex-grow: 1; } .affiliates-space { display: flex; overflow-x: hidden; flex: 1 1 auto; min-width: 0; width: 100%; max-width: 100%; flex-wrap: nowrap; min-height: 40px; align-items: center; } .affiliates-and-arrows .new-augment-button.affiliates-scroll-disabled { opacity: 0.35; pointer-events: none; } .affiliates-space .free-driver { min-width: 200px; max-width: 200px; flex: 0 0 auto; /* Asegura que los elementos mantengan su tamaño */ } .team-template .free-driver { margin: 1px 3px; } .driver1-name, .driver2-name { display: flex; flex-direction: column; color: var(--white-general); background-color: var(--elements); border-radius: 10px; width: 100%; margin-left: 5px; margin-right: 5px; justify-content: center; align-items: center; transition: background-color 0.15s, color 0.15s; box-shadow: var(--shadow-s); position: relative; } .driver1-name[class*="-back-normal"]::after, .driver2-name[class*="-back-normal"]::after { opacity: 0.45; filter: saturate(0.75); } .driver1-first, .driver1-second, .driver2-first, .driver2-second, .team1, .team2 { z-index: 1; } .driver1-name:hover, .driver2-name:hover { color: var(--white-general); } .driver1-second, .driver2-second { margin-top: -6px; } .part-performance-title:has(.arrows.d-none) .part-name-buttons{ margin-left: 0px; } .part-name-buttons { display: flex; flex-direction: row; gap: 10px; margin-left: 5px; flex-grow: 1; } .part-buttons { display: flex; flex-direction: row; align-items: center; gap: 5px; } .part-performance-title .redesigned-chevron{ height: 28px; cursor: pointer; transition: background-color 0.15s, transform 0.15s; } .part-performance-title .redesigned-chevron:hover{ background-color: var(--new-primary); } .custom-engines-div { display: flex; flex-direction: column; gap: 5px; } .add-engine { border: 2px dashed var(--dark-text); color: var(--dark-text); display: flex; flex-direction: row; align-items: center; justify-content: space-between; border-radius: 8px; transition: color 0.15s, border 0.15s, transform 0.15s; padding: 8px; font-size: 18px; } .add-engine i { font-style: normal; } .custom-engines-div .engine-performance-stats { margin-top: 2px; border-top: 2px solid var(--separator); } .engine-performance .bi-trash { cursor: pointer; transition: color 0.15s, transform 0.15s; color: var(--text-general); position: absolute; right: 40px; top: 5px; font-size: 20px; } .engine-performance .bi-trash:hover { color: var(--negative-general); transform: scale(1.05); } .engine-performance .redesigned-chevron { cursor: pointer; transition: color 0.15s, transform 0.15s; color: var(--text-general); position: absolute; right: 14px; top: 7px; height: 26px; } .engine-performance .redesigned-chevron:hover { color: var(--text-selected); } .engine-performance .custom-engine-flag { position: absolute; right: 14px; top: 7px; font-size: 20px; color: var(--text-tertiary); pointer-events: auto; cursor: pointer; padding: 2px 6px; border-radius: 6px; transition: color 0.15s, background-color 0.15s, background 0.15s; } .engine-performance .custom-engine-flag.bi-trash { background: color-mix(in srgb, var(--negative-general) 30%, transparent); color: var(--negative-general); } .engine-performance .custom-engine-flag.bi-trash:active{ transform: scale(0.97); } .custom-engine-title { max-width: 100%; width: 100%; } .custom-engine-name { background-color: transparent; border: 0; outline: none; color: inherit; width: 100%; min-width: 0; font: inherit; text-transform: inherit; field-sizing: content; } .part-performance, .engine-performance { background-color: var(--superficials); border-radius: 10px; box-shadow: var(--shadow-s); padding: 5px 10px; overflow-y: hidden; position: relative; } .main-columns-drag-section .engine-performance { min-height: 318px; } .blank-engine-space{ height: 100px; width: 100%; } .engine-f2 { border-bottom: 2px solid var(--f2-border); } .engine-f3 { border-bottom: 2px solid var(--f3-border); } .engine-fe { border-bottom: 2px solid var(--ferrari-primary); } .engine-rb { border-bottom: 2px solid var(--redbull-primary); } .engine-re { border-bottom: 2px solid var(--renault-primary); } .engine-ho { border-bottom: 2px solid var(--honda-primary); } .engine-me { border-bottom: 2px solid var(--mercedes-primary); } .engine-al { border-bottom: 2px solid var(--alpine-primary); } .engine-wi { border-bottom: 2px solid var(--williams-primary); } .engine-af { border-bottom: 2px solid var(--alfa-primary); } .engine-at { border-bottom: 2px solid var(--alphatauri-primary); } .engine-ha { border-bottom: 2px solid var(--haas-primary); } .engine-mc { border-bottom: 2px solid var(--mclaren-primary); } .engine-ct { border-bottom: 2px solid var(--custom-team-primary); } .engine-as { border-bottom: 2px solid var(--aston-primary); } input.engine-performance-title { background-color: transparent; border-bottom: 0px solid; border-right: 0px solid; border-left: 0px solid; border-top: 0px solid; } input.engine-performance-title:hover, input.engine-performance-title:focus { outline: none; color: var(--text-selected); } .part-performance-title, .engine-performance-title { text-transform: uppercase; font-family: 'Formula1Bold'; font-size: 20px; color: var(--white-general); transition: color 0.15s, border-color 0.15s; } .part-performance-title { display: flex; justify-content: flex-start; position: relative; } .engine-performance-title { display: inline-flex; align-items: center; gap: 8px; max-width: max-content; } .engine-performance-logo { width: 30px; height: 20px; object-fit: contain; } .engine-modal-body { display: flex; flex-direction: column; gap: 10px; max-height: 600px; min-height: 300px; overflow-y: scroll; padding: 0 10px 5px 10px !important; } .part-performance-stat, .engine-performance-stat { display: flex; justify-content: space-between; color: var(--text-general); } .engine-performance-stat { position: relative; align-items: center; margin: 4px 0; } .engine-performance-bar { position: absolute; bottom: 1px; left: 0; height: 2px; width: 83%; } .engine-performance-progress { width: 50%; height: 2px; transition: width 0.15s; background-color: var(--new-primary); } .part-performance-title i.clicked { color: var(--new-primary); } .parts-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; overflow-y: auto; position: absolute; top: 30px; width: 95%; transition: all 0.15s; height: calc(100% - 50px); padding-right: 5px; } .one-part { display: flex; flex-direction: row; justify-content: space-between; } .one-part-default { border-bottom: 2px solid var(--separator); margin-bottom: 3px; padding: 3px 0; } .one-part-title { font-size: 18px; font-family: 'Formula1Bold'; color: var(--dark-text); display: flex; } .one-part-flag-and-text { position: relative; font-family: 'Formula1Bold'; font-size: 16px; color: var(--text-general); } .one-part-flag { width: 48px; height: 27px; border-radius: 5px; border: none; opacity: 0.45; } .one-part-flag-title { position: absolute; top: 45%; left: 50%; transform: translate(-50%, -50%); text-shadow: 2px 2px 4px var(--negative-text); } .part-performance-stats, .engine-performance-stats { margin-top: 8px; transition: all 0.15s; } .part-performance-stats { display: flex; flex-direction: column; justify-content: space-between; position: absolute; top: 30px; width: 95%; height: 89% !important; } .part-performance-stats:not(.hidden) { opacity: 1; pointer-events: all; transform: translateY(0px); } .part-performance-stats.hidden { opacity: 0; pointer-events: none; transform: translateY(20px); } .part-performance:has(.part-performance-stats.hidden) .parts-list { opacity: 1; } .part-performance:has(.part-performance-stats:not(.hidden)) .parts-list { opacity: 0; } .fit-button { font-family: 'Formula1Bold'; color: var(--text-general); transition: color 0.15s, transform 0.15s; margin-left: 4px; } .fit-button:hover, .reset-mentality-button:hover { cursor: pointer; color: var(--new-primary); transform: scale(1.05); } .n-parts-error { animation: errorAnim 0.5s; } .loadout-1-error { animation: errorLoadout1 0.5s; } .loadout-2-error { animation: errorLoadout2 0.5s; } @keyframes errorAnim { 0% { color: var(--negative-general); } 100% { color: var(--dark-text); } } @keyframes errorLoadout1 { 0% { color: var(--negative-general); } 100% { color: var(--new-primary); } } @keyframes errorLoadout2 { 0% { color: var(--negative-general); } 100% { color: var(--new-secondary); } } .fitted-icons { margin-left: 10px; display: flex; flex-direction: row; font-size: 25px; max-height: 30px; align-items: center; } .spare { min-width: 25px; } .one-part-name { transition: color 0.15s, transform 0.15s; } .one-part-name:hover { cursor: pointer; color: var(--text-selected); transform: scale(1.05); } .one-part-name.editing { color: var(--text-general); } .part-subtitle { color: var(--dark-text); font-size: 20px; margin-left: -7px; } .fitted-icons .n-parts-buttons { display: flex; flex-direction: row-reverse; gap: 3px; } .fitted-icons .n-parts-buttons i { font-size: 14px; } .new-part { border: none; border-radius: 10px; padding-left: 4px; cursor: pointer; max-width: max-content; transition: border-color 0.15s, color 0.15s, transform 0.15s; color: var(--dark-text); } .new-part i { font-style: normal; } .new-part:hover { color: var(--new-primary); transform: scale(1.05); } .new-part ::before { margin-right: 5px; } .fitted-icons .n-parts { font-family: "Formula1"; font-size: 14px; max-height: 30px; margin-right: 5px; } .fitted-icons i:not(.fitted) { transition: color 0.15s; } .fitted-icons .loadout-1.fitted { color: var(--new-primary); } .fitted-icons .loadout-1 { position: relative; padding-bottom: 4px; margin-left: 6px; } .loadout-1:not(.fitted):hover, .loadout-2:not(.fitted):hover { cursor: pointer; transform: scale(1.05); } .loadout-1:not(.fitted):hover { color: var(--text-selected); } .loadout-2:not(.fitted):hover { color: var(--text-selected-secondary); } .loadout-1 .number, .loadout-2 .number { font-size: 10px; font-family: 'Formula1Bold'; position: absolute; bottom: 7px; right: 3px; z-index: 2; padding-bottom: 4px; } .fitted-icons .loadout-2 { position: relative; padding-bottom: 4px; } .fitted-icons .loadout-2.fitted { color: var(--new-secondary); } .new-or-existing-part { font-family: 'Formula1Bold'; display: flex; gap: 15px; padding: 6px 2px; color: var(--dark-text); } .existing-part { font-size: 16px; transition: color 0.15s, transform 0.15s; } .existing-part:hover { cursor: pointer; color: var(--text-selected); transform: scale(1.05); } .existing-part.active-part { color: var(--new-primary); } .part-performance-stat .custom-input-number, .engine-performance-stat .custom-input-number { font-size: 16px; max-width: 70px; } .teams-show.expertise-mode .part-performance-stat .custom-input-number { color: var(--new-primary); } .overview-show { background-color: transparent !important; box-shadow: none !important; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-rows: repeat(2, minmax(0, 1fr)); gap: 8px; flex: 1; padding-right: 7px; overflow-y: auto; } .overview-card { background-color: var(--superficials); border-radius: 10px; padding: 8px; display: flex; flex-direction: column; min-height: 0; box-shadow: var(--shadow-s); } .overview-card-title { font-size: 15px; color: var(--text-secondary); margin-bottom: 6px; } .overview-card-teams { display: flex; gap: 2px; overflow-y: auto; min-height: 0; justify-content: space-between; flex-direction: column; height: 100%; } .overview-team .car-title { font-size: 11px; height: auto; padding-bottom: 0; } .overview-team-left { display: flex; align-items: center; gap: 4px; min-width: 0; } .overview-team-rank { color: var(--text-secondary); font-size: 11px; min-width: 12px; text-align: right; } .overview-team .performance-bar { height: 3px; margin: 1px 0 2px; } .overview-team .performance-bar-progress { height: 3px; } .overview-team-value { color: var(--dark-text); font-size: 11px; } .teams-show, .engines-show { background-color: transparent !important; box-shadow: none !important; display: grid; grid-template-columns: repeat(3, 0.333fr); grid-template-rows: repeat(2, 1fr); gap: 8px; flex: 1; } .main-columns-drag-section:has(.teams-show:not(.d-none)), .main-columns-drag-section:has(.overview-show:not(.d-none)){ background-color: transparent !important; box-shadow: none !important; } .engines-show { /* Keep the grid layout but allow any number of rows (custom engines are appended). */ grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: unset; grid-auto-rows: max-content; align-content: start; padding-right: 7px; max-height: 81vh; } .one-performance { padding: 10px; display: flex; flex-direction: row; align-items: center; font-size: 22px; padding-left: 20px; background-color: var(--component-general); border-radius: 10px; margin-top: 10px; margin-left: 10px; margin-right: 10px; color: var(--text-general); border: 2px solid transparent; transition: border-color 0.15s, background-color 0.15s; } .one-performance:hover { border-color: var(--white-general); background-color: var(--component-general-hover); } .one-performance:hover .performance-name { color: var(--white-general); } .performance-buttons { padding: 10px; display: flex; width: 100px; justify-content: space-around; } .perf-engine { font-size: 20px; } .performance-buttons i { cursor: pointer; background-color: transparent; border-radius: 10px; transition: background-color 0.15s, color 0.15s; background-color: transparent; width: 25px; height: 25px; display: flex; justify-content: center; align-items: center; } .performance-buttons i:hover { background-color: var(--background--buttons-hover) !important; color: var(--white-general) !important; } .performance-buttons i:active { background-color: var(--active-buttons-back) !important; color: #e2e2e2 !important; transition-duration: 0s; } .performance-name { width: 200px; transition: color 0.15s; cursor: default !important; } .performance-name-engine { width: 300px; transition: color 0.15s; cursor: default !important; } .custom-progress { width: 603px; display: flex; flex-direction: row; align-items: center; margin-left: 50px; } .name-H2H { width: 100%; transition: color 0.15s; cursor: default !important; text-align: center; } .only-name { transition: color 0.15s; } #bestrh2h .name-H2H, #raceh2h .name-H2H, #qualih2h .name-H2H { display: flex; justify-content: space-between; } .custom-H2H { width: 75%; display: flex; flex-direction: row; background-color: var(--h2h-back); height: 7px; margin-top: 5px; } .driver1-bar { float: right; width: 0%; height: 7px; transition: width 0.15s; } .h2h-top { display: flex; flex-direction: row; justify-content: space-between } .driver2-bar { float: left; width: 0%; height: 7px; transition: width 0.15s; } .driver2-space, .driver1-space { width: 50%; background-color: var(--background); height: 7px; } i.disabled { pointer-events: none; color: var(--disabled-text); } input.disabled { pointer-events: none; color: var(--disabled-text); } .red-part { width: 300px; height: 3px; background-color: var(--negative-general); position: relative; } .disabled-part { width: 300px; height: 3px; background-color: var(--background-general); position: relative; } .disabled-part-engine { width: 100%; height: 3px; background-color: var(--background-general); position: relative; } .green-part { width: 0%; height: 100%; background-color: var(--positive-general); position: absolute; top: 0; left: 0; z-index: 1; transition: width 0.15s; } .white-part { width: 0%; height: 100%; background-color: #f0f0f0; position: absolute; top: 0; left: 0; z-index: 1; transition: width 0.15s; } .separation-zone { width: 3px; height: 10px; background-color: #f0f0f0; } .gray-part { width: 100%; height: 100%; background-color: var(--background-general); position: absolute; top: 0; left: 0; z-index: 1; transition: width 0.15s; } .car-info { font-size: 14px; } body.og-theme .teams-columns, body.og-theme .records-list { padding: 12px 10px; } .teams-columns { display: flex; flex-direction: column; overflow-y: hidden; margin-left: 0%; margin-right: 14px; background: transparent; box-shadow: none; gap: 10px; } .performance-bar { width: 300px; } .performance-section { overflow: hidden; margin-left: 0%; margin-right: 14px; display: flex; height: 100%; } .btn-number { background-color: transparent !important; color: var(--white-general) !important; padding-left: 0px !important; border: none !important; } .collapse-number { color: var(--white-general); background-color: var(--custom-modal-back); } .contract-details { display: grid; grid-template-columns: repeat(3, 1fr); color: var(--white-general); } .number-and-contract { display: flex; flex-direction: column; background-color: var(--custom-modal-back); } .custom-body { color: var(--text-general); background-color: var(--generals); z-index: -2; padding: 0px 20px 5px 20px; } .pos-relative { position: relative; } .custom-modal { min-width: 600px !important; z-index: 2000 !important; border-radius: 10px !important; } #patreonModal .custom-modal, #patreonModal .modal-dialog { min-width: 800px !important; } .drivers-modal-zone, .teams-modal-zone { border-radius: 10px; width: 100%; display: grid; gap: 12px; background-color: transparent; grid-template-columns: repeat(4, 1fr); } .contract-details div { color: var(--text-general); } .custom-header { color: var(--white-general) !important; background-color: var(--generals) !important; display: flex; justify-content: space-between; border-bottom: none !important; flex-direction: column; align-items: flex-start; position: relative; } .custom-footer { color: var(--white-general) !important; background-color: var(--generals) !important; z-index: -3; border-top: none !important; padding: 5px 5px; } ::-webkit-input-placeholder { color: #999; /* Cambia el color del texto del placeholder */ } .rounded-input { border: 2px solid transparent; border-radius: 10px; padding: 8px 12px; max-width: 210px; outline: none; transition: border-color 0.15s; background-color: var(--mid-gray-contrast); color: #ebeaf0; } .rounded-input.retirement { max-width: 75px; } .rounded-input.retirement::selection, .modal-subtitle::selection, #difficultySpan::selection { background-color: transparent; } .driver1-number::selection, .driver2-number::selection { background-color: transparent; } .custom-disabled { background-color: -internal-light-dark(rgba(239, 239, 239, 0.3), rgba(59, 59, 59, 0.3)); } .rounded-input:hover { border-color: #cecece; } .rounded-input:focus { outline: 0px solid transparent; border-color: var(--white-general); color: var(--white-general); } .confirm-area { display: flex; justify-content: center; align-items: center; padding-top: 50px; } .performance-data { margin-left: 30px; font-size: 26px; width: 200px; text-align: center; } .team, .engine { border: 2px solid transparent; border-radius: 10px; padding: 4.8px; cursor: pointer !important; margin: 10px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 18px; } .team { margin: 0; border: none; padding: 5.8px; transition: background-color 0.15s; } .car { border: 1px solid transparent; border-radius: 7px; padding: 4px; cursor: pointer !important; margin: 4px 6px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 18px; } .driver-info { border-radius: 10px; cursor: default !important; padding: 10px; margin: 9px 8px; color: var(--text-general); background-color: var(--dropdown-hover); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 18px; display: flex; flex-direction: row; box-shadow: 0px 0px 10px 1px var(--box-shadow); } .driver-info:hover { background-color: var(--elements-hover); } .race:hover { background-color: var(--text-general-transparent); color: var(--negative-text); } .rb-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--redbull-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .rb-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--redbull-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .rb-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--redbull-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .rb-gradient:hover::before, .rb-gradient-small:hover::before, .rb-gradient-mid:hover::before { opacity: 1; } .rb-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--redbull-primary), var(--elements) 35%); } .rb-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--redbull-secondary), var(--elements) 35%); } .mc-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--mclaren-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .mc-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--mclaren-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .mc-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--mclaren-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .mc-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--mclaren-primary), var(--elements) 35%); } .mc-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--mclaren-secondary), var(--elements) 35%); } .mc-gradient:hover::before, .mc-gradient-small:hover::before, .mc-gradient-mid:hover::before { opacity: 1; } .as-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--aston-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .as-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--aston-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .as-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--aston-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .as-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--aston-primary), var(--elements) 35%); } .as-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--aston-secondary), var(--elements) 35%); } .as-gradient:hover::before, .as-gradient-small:hover::before, .as-gradient-mid:hover::before { opacity: 1; } .al-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alpine-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .al-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--alpine-primary-transparent), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .al-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alpine-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .al-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--alpine-primary), var(--elements) 35%); } .al-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--alpine-secondary), var(--elements) 35%); } .al-gradient:hover::before, .al-gradient-small:hover::before, .al-gradient-mid:hover::before { opacity: 1; } .af-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alfa-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .af-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alfa-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .af-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--alfa-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .af-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--alfa-primary), var(--elements) 35%); } .af-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--alfa-secondary), var(--elements) 35%); } .af-gradient:hover::before, .af-gradient-small:hover::before, .af-gradient-mid:hover::before { opacity: 1; } .ha-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--haas-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .ha-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--haas-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .ha-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--haas-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .ha-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--haas-primary), var(--elements) 35%); } .ha-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--haas-secondary), var(--elements) 35%); } .ha-gradient:hover::before, .ha-gradient-small:hover::before, .ha-gradient-mid:hover::before { opacity: 1; } .wi-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--williams-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .wi-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--williams-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .wi-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--williams-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .wi-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--williams-primary), var(--elements) 35%); } .wi-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--general-secondary), var(--elements) 35%); } .wi-gradient:hover::before, .wi-gradient-small:hover::before, .wi-gradient-mid:hover::before { opacity: 1; } .at-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alphatauri-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .at-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--alphatauri-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .at-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--alphatauri-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .at-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--alphatauri-primary), var(--elements) 35%); } .at-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--alphatauri-secondary), var(--elements) 35%); } .at-gradient:hover::before, .at-gradient-small:hover::before, .at-gradient-mid:hover::before { opacity: 1; } .fe-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--ferrari-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .fe-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--ferrari-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .fe-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--ferrari-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .fe-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--ferrari-primary), var(--elements) 35%); } .fe-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--general-secondary), var(--elements) 35%); } .fe-gradient:hover::before, .fe-gradient-small:hover::before, .fe-gradient-mid:hover::before { opacity: 1; } .me-gradient::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--mercedes-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .me-gradient-mid::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 150%, var(--mercedes-primary), transparent 25%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .me-gradient-small::before { border-radius: 10px; content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 200%, var(--mercedes-primary-transparent), transparent 30%); opacity: 0; transition: opacity 0.15s; z-index: 0; } .me-back-gradient-primary { background: radial-gradient(circle at 5% 100%, var(--mercedes-primary), var(--elements) 35%); } .me-back-gradient-secondary { background: radial-gradient(circle at 5% 100%, var(--general-secondary), var(--elements) 35%); } .me-gradient:hover::before, .me-gradient-small:hover::before, .me-gradient-mid:hover::before { opacity: 1; } .race { border-radius: 10px; cursor: pointer !important; margin: 10px; color: var(--text-general); background-color: var(--elements); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 24px; text-transform: uppercase; padding: 5px 0; padding-left: 8px; display: flex; align-items: center; justify-content: space-between; box-shadow: 0px 0px 10px 1px var(--box-shadow); } .race-flag { min-width: 80px; max-width: 80px; min-height: 45px; max-height: 45px; border-radius: 10px; z-index: 2; margin-right: 4px; } #patchNotesBody { max-height: 600px; overflow-y: scroll; } #raceMenu { overflow-y: scroll; height: 660px; } .config-content { color: var(--text-selected); font-size: 20px; } .toolbar-logo-and-title { display: flex; flex-direction: row; gap: 8px; padding: 10px; align-items: center; cursor: pointer; } .toolbar-logo { width: 34px; height: 34px; object-fit: contain; } .toolbar-logo--ferrari { width: 34px; height: 26px; } .toolbar-logo--redbull { width: 34px; height: 34px; } .toolbar-logo--nightly { width: 34px; height: 30px; } .toolbar-logo--mclaren { width: 34px; height: 30px; } .toolbar-logo--mercedes { width: 34px; height: 24px; } .toolbar-logo--astonmartin { width: 34px; height: 34px; } .toolbar-logo--haas{ width: 34px; height: 26px; } .toolbar-title { font-size: 16px; display: flex; flex-direction: column; justify-content: flex-start; color: transparent; position: relative; /* Necesario para aplicar el gradiente */ } .toolbar-title span { display: inline-block; line-height: 1; background: linear-gradient(to right, var(--new-primary), var(--text-general), var(--new-primary)); background-clip: text; -webkit-text-fill-color: transparent; -webkit-background-clip: text; background-size: 200% 200%; animation: gradient-move-title 6s infinite linear; } .toolbar-title.nightly span { display: inline-block; line-height: 1; background: linear-gradient(to right, var(--new-primary), var(--text-general), var(--new-primary)); background-clip: text; -webkit-text-fill-color: transparent; -webkit-background-clip: text; background-size: 200% 200%; animation: gradient-move-title 6s infinite linear; } .patreon-links { display: flex; flex-direction: row; gap: 10px; } .patreon-themes .modal-subtitle { margin-bottom: 3px; } .nightly-icon { position: absolute; top: 0px; right: 0px; width: 14px; height: 14px; font-size: 8px; background-color: var(--new-primary); border-radius: 50%; color: var(--elements); display: flex; align-items: center; justify-content: center; } @keyframes gradient-move-title { 0% { background-position: 0% 50%; } 100% { background-position: -200% 50%; /* Doble del tamaño inicial para un bucle suave */ } } #predictConfigContent { margin-top: 12px; } #predictionMenu { margin-left: 0px; margin-right: 14px; width: 100%; display: flex; flex-direction: row; } .prediction-table { width: 50%; } .prediction-table-header, .prediction-table-driver { display: flex; flex-direction: row; color: var(--text-general); font-size: 20px; margin: 20px 20px; } .driver-prediction { width: 50%; z-index: 100; } .prediction-table-header .driver-prediction, .prediction-table-header .position-prediction, .prediction-table-header .prediction-prediction { text-transform: uppercase; } .position-prediction { width: 25%; display: flex; justify-content: center; gap: 15px; padding-left: 40px; z-index: 100; } .position-prediction> :first-child { min-width: 50px; margin-left: 20px; } .position-prediction> :nth-child(2) { min-width: 50px; } .prediction-prediction { width: 25%; display: flex; justify-content: center; gap: 15px; z-index: 100; } .front-gradient { z-index: 10; } .rb-transparent:hover { background-color: var(--redbull-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .rb-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--redbull-primary)); } .rb-transparent:hover .surname { color: var(--redbull-primary) !important } .rb-back-transparent { background-color: var(--redbull-primary-transparent); } .rb-back-transparent-secondary { background-color: var(--redbull-secondary-transparent); } .mc-transparent:hover { background-color: var(--mclaren-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .mc-transparent:hover .surname { color: var(--mclaren-primary) !important } .mc-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--mclaren-primary)); } .mc-back-transparent { background-color: var(--mclaren-primary-transparent); } .mc-back-transparent-secondary { background-color: var(--mclaren-secondary-transparent); } .as-transparent:hover { background-color: var(--aston-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .as-transparent:hover .surname { color: var(--aston-primary) !important } .as-back-transparent { background-color: var(--aston-primary-transparent); } .as-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--aston-primary)); } .as-back-transparent-secondary { background-color: var(--aston-secondary-transparent); } .al-transparent:hover { background-color: var(--alpine-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .al-transparent:hover .surname { color: var(--alpine-primary) !important } .al-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--alpine-primary)); } .al-back-transparent { background-color: var(--alpine-primary-transparent); } .al-back-transparent-secondary { background-color: var(--alpine-secondary-transparent); } .af-transparent:hover { background-color: var(--alfa-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .af-transparent:hover .surname { color: var(--alfa-primary) !important } .af-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--alfa-primary)); } .af-back-transparent { background-color: var(--alfa-primary-transparent); } .af-back-transparent-secondary { background-color: var(--alfa-secondary-transparent); } .ha-transparent:hover { background-color: var(--haas-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .ha-transparent:hover .surname { color: var(--haas-primary) !important } .ha-back-transparent { background-color: var(--haas-primary-transparent); } .ha-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--haas-primary)); } .ha-back-transparent-secondary { background-color: var(--haas-secondary-transparent); } .wi-transparent:hover { background-color: var(--williams-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .wi-transparent:hover .surname { color: var(--williams-primary) !important } .wi-back-transparent { background-color: var(--williams-primary-transparent); } .wi-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--williams-primary)); } .wi-back-transparent-secondary { background-color: var(--general-secondary-transparent); } .at-transparent:hover { background-color: var(--alphatauri-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .at-transparent:hover .surname { color: var(--alphatauri-primary) !important } .at-back-transparent { background-color: var(--alphatauri-primary-transparent); } .at-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--alphatauri-primary)); } .at-back-transparent-secondary { background-color: var(--alphatauri-secondary-transparent); } .fe-transparent:hover { background-color: var(--ferrari-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .fe-transparent:hover .surname { color: var(--ferrari-primary) !important; } .fe-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--ferrari-primary)); } .fe-back-transparent { background-color: var(--ferrari-primary-transparent); } .fe-back-transparent-secondary { background-color: var(--general-secondary-transparent); } .me-transparent:hover { background-color: var(--mercedes-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .me-transparent:hover .surname { color: var(--mercedes-primary) !important; } .me-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--mercedes-primary)); } .me-back-transparent { background-color: var(--mercedes-primary-transparent); } .me-back-transparent-secondary { background-color: var(--general-secondary-transparent); } .ct-transparent .custom-icon i { filter: drop-shadow(0px 0px 4px var(--custom-team-primary)); } .ct-transparent:hover { background-color: var(--custom-team-primary-transparent) !important; color: var(--white-general) !important; border-color: transparent !important; } .ct-back-transparent { background-color: var(--custom-team-primary-transparent); } .ct-back-transparent-secondary { background-color: var(--custom-team-secondary-transparent); } .ct-transparent:hover .surname { color: var(--custom-team-primary) !important; } .rb-transparent.selected { background-color: var(--redbull-primary-transparent) !important; color: var(--white-general) !important; } .mc-transparent.selected { background-color: var(--mclaren-primary-transparent) !important; color: var(--white-general) !important; } .as-transparent.selected { background-color: var(--aston-primary-transparent) !important; color: var(--white-general) !important; } .al-transparent.selected { background-color: var(--alpine-primary-transparent) !important; color: var(--white-general) !important; } .af-transparent.selected { background-color: var(--alfa-primary-transparent) !important; color: var(--white-general) !important; } .ha-transparent.selected { background-color: var(--haas-primary-transparent) !important; color: var(--white-general) !important; } .wi-transparent.selected { background-color: var(--williams-primary-transparent) !important; color: var(--white-general) !important; } .at-transparent.selected { background-color: var(--alphatauri-primary-transparent) !important; color: var(--white-general) !important; } .fe-transparent.selected { background-color: var(--ferrari-primary-transparent) !important; color: var(--white-general) !important; } .me-transparent.selected { background-color: var(--mercedes-primary-transparent) !important; color: var(--white-general) !important; } .ct-transparent.selected { background-color: var(--custom-team-primary-transparent) !important; color: var(--white-general) !important; } .modal-team.rb-transparent:hover { background-color: var(--redbull-primary-transparent); color: var(--redbull-primary); } .modal-team.mc-transparent:hover { background-color: var(--mclaren-primary-transparent); color: var(--mclaren-primary); } .modal-team.as-transparent:hover { background-color: var(--aston-primary-transparent); color: var(--aston-primary); } .modal-team.al-transparent:hover { background-color: var(--alpine-primary-transparent); color: var(--alpine-primary); } .modal-team.af-transparent:hover { background-color: var(--alfa-primary-transparent); color: var(--alfa-primary); } .modal-team.ha-transparent:hover { background-color: var(--haas-primary-transparent); color: var(--haas-primary); } .modal-team.wi-transparent:hover { background-color: var(--williams-primary-transparent); color: var(--williams-primary); } .modal-team.at-transparent:hover { background-color: var(--alphatauri-primary-transparent); color: var(--alphatauri-primary); } .modal-team.fe-transparent:hover { background-color: var(--ferrari-primary-transparent); color: var(--ferrari-primary); } .modal-team.me-transparent:hover { background-color: var(--mercedes-primary-transparent); color: var(--mercedes-primary); } .free-driver:hover .future-contract-noti { opacity: 0; } .future-contract-noti { position: absolute; top: 5px; right: 5px; width: 6px; height: 6px; border-radius: 50%; animation: pulse 1.5s infinite; opacity: 1; transition: opacity 0.15s; } .noti-fe { background-color: var(--ferrari-primary); } .noti-fe-affiliate{ background-color: transparent; border: 1px solid var(--ferrari-primary); } .noti-rb { background-color: var(--redbull-primary); } .noti-rb-affiliate{ background-color: transparent; border: 1px solid var(--redbull-primary); } .noti-mc { background-color: var(--mclaren-primary); } .noti-mc-affiliate{ background-color: transparent; border: 1px solid var(--mclaren-primary); } .noti-as { background-color: var(--aston-primary); } .noti-as-affiliate{ background-color: transparent; border: 1px solid var(--aston-primary); } .noti-al { background-color: var(--alpine-primary); } .noti-al-affiliate{ background-color: transparent; border: 1px solid var(--alpine-primary); } .noti-af { background-color: var(--alfa-primary); } .noti-af-affiliate{ background-color: transparent; border: 1px solid var(--alfa-primary); } .noti-ha { background-color: var(--haas-primary); } .noti-ha-affiliate{ background-color: transparent; border: 1px solid var(--haas-primary); } .noti-wi { background-color: var(--williams-primary); } .noti-wi-affiliate{ background-color: transparent; border: 1px solid var(--williams-primary); } .noti-at { background-color: var(--alphatauri-primary); } .noti-at-affiliate{ background-color: transparent; border: 1px solid var(--alphatauri-primary); } .noti-me { background-color: var(--mercedes-primary); } .noti-me-affiliate{ background-color: transparent; border: 1px solid var(--mercedes-primary); } .noti-ct { background-color: var(--custom-team-primary); } .noti-ct-affiliate{ background-color: transparent; border: 1px solid var(--custom-team-primary); } .name-container { display: flex; overflow: hidden; white-space: nowrap; position: relative; min-width: max-content; gap: 5px; } .name-container span { white-space: nowrap; } @keyframes marquee { 0% { transform: translateX(0); } 50% { transform: translateX(calc(-1 * var(--scroll-amount))); } 100% { transform: translateX(0); } } .free-driver .edit-container { display: flex; gap: 5px; position: absolute; left: -5px; top: -6px; width: 39px; opacity: 0; color: var(--text-general); transition: opacity 0.15s, background-color 0.15s, color 0.15s; cursor: pointer; font-size: 14px; border-bottom-right-radius: 3px; z-index: 3; padding-left: 8px; padding-top: 1px; height: 17px; align-items: center; } .free-driver .bi-pencil-fill { font-size: 8px; height: 8PX; } .free-driver .bi-123 { height: 17px; } .free-driver:hover .edit-container { opacity: 1; } .free-driver .edit-container:hover { background-color: var(--text-general); color: var(--negative-text); } .free-driver { background-color: var(--elements); border-radius: 9px; padding: 5px 6px; cursor: grab !important; margin: 6px 5px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 15px; overflow: hidden; height: 36px; display: flex; gap: 3px; box-shadow: var(--shadow-s); align-items: center; } .free-driver span:not(.bold-font) { max-height: 22.5px; } .drivers-table-row .drivers-table-driver span:not(.bold-font), .viewer-header-driver span:not(.bold-font) { max-height: 22.5px; white-space: nowrap; } .main-viewer-section:has(.records-list:not(.d-none)) { background-color: transparent; box-shadow: none; } .records-list { display: flex; flex-direction: column; gap: 10px; background-color: transparent; overflow-y: auto; max-height: calc(100vh - 160px); padding-right: 5px; } .record-surname { text-transform: uppercase; } .records-list .record-item { width: 100%; display: flex; flex-direction: row; justify-content: space-between; background-color: var(--superficials); border-radius: 10px; padding: 10px 20px; align-items: center; height: 67px; position: relative; box-shadow: var(--shadow-s); } .number-and-name { display: flex; flex-direction: row; gap: 20px; align-items: center; z-index: 2; } .record-number { font-size: 22px; font-family: 'NumbersBold'; padding-top: 4px; width: 37px; display: flex; justify-content: center; } .record-logo-div{ display: flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin: 0 -10px; } .record-logo-div img, .record-logo-div div { width: 40px; height: 40px; } .record-team-drivers{ display: flex; flex-direction: row; align-items: center; gap: 8px; flex-wrap: wrap; } .record-drivers-separator{ color: var(--text-general); user-select: none; } .record-team-driver{ display: inline-flex; align-items: center; gap: 6px; } .record-driver-number{ font-family: 'NumbersFont'; padding-top: 1px; height: 21px; } .record-name { font-size: 20px; color: var(--text-general); line-height: 1.3; } .record-value { font-size: 22px; font-family: 'NumbersFont'; padding-top: 4px; z-index: 2; } .name-and-team { display: flex; flex-direction: column; align-items: flex-start; } .record-team { font-size: 14px; color: var(--engine-table-name); } .rb-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--redbull-primary), transparent 30%); border-radius: 10px; z-index: 0; } .rb-record .record-team { color: var(--redbull-primary); } .rb-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--redbull-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .rb-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--redbull-primary), transparent); border-radius: 10px; z-index: 0; } .rb-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--redbull-secondary), transparent); border-radius: 10px; z-index: 0; } .mc-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--mclaren-primary), transparent 30%); border-radius: 10px; z-index: 0; } .mc-record .record-team { color: var(--mclaren-primary); } .mc-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--mclaren-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .mc-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--mclaren-primary), transparent); border-radius: 10px; z-index: 0; } .mc-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--mclaren-secondary), transparent); border-radius: 10px; z-index: 0; } .as-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--aston-primary), transparent 30%); border-radius: 10px; z-index: 0; } .as-record .record-team { color: var(--aston-primary); } .as-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--aston-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .as-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--aston-primary), transparent); border-radius: 10px; z-index: 0; } .as-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--aston-secondary), transparent); border-radius: 10px; z-index: 0; } .al-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--alpine-primary), transparent 30%); border-radius: 10px; z-index: 0; } .al-record .record-team { color: var(--alpine-primary); } .al-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--alpine-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .al-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alpine-primary), transparent); border-radius: 10px; z-index: 0; } .al-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alpine-secondary), transparent); border-radius: 10px; z-index: 0; } .af-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--alfa-primary), transparent 30%); border-radius: 10px; z-index: 0; } .af-record .record-team { color: var(--alfa-primary); } .af-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--alfa-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .af-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alfa-primary), transparent); border-radius: 10px; z-index: 0; } .af-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alfa-secondary), transparent); border-radius: 10px; z-index: 0; } .ha-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--haas-primary), transparent 30%); border-radius: 10px; z-index: 0; } .ha-record .record-team { color: var(--haas-primary); } .ha-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--haas-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .ha-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--haas-primary), transparent); border-radius: 10px; z-index: 0; } .ha-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--haas-secondary), transparent); border-radius: 10px; z-index: 0; } .wi-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--williams-primary), transparent 30%); border-radius: 10px; z-index: 0; } .wi-record .record-team { color: var(--williams-primary); } .wi-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--williams-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .wi-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--williams-primary), transparent); border-radius: 10px; z-index: 0; } .wi-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--general-secondary), transparent); border-radius: 10px; z-index: 0; } .at-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--alphatauri-primary), transparent 30%); border-radius: 10px; z-index: 0; } .at-record .record-team { color: var(--alphatauri-primary); } .at-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--alphatauri-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .at-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alphatauri-primary), transparent); border-radius: 10px; z-index: 0; } .at-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--alphatauri-secondary), transparent); border-radius: 10px; z-index: 0; } .fe-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--ferrari-primary), transparent 30%); border-radius: 10px; z-index: 0; } .fe-record .record-team { color: var(--ferrari-primary); } .fe-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--ferrari-primary), transparent 120%); z-index: 0; border-radius: 0px; } .fe-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--ferrari-primary), transparent); border-radius: 10px; z-index: 0; } .fe-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--general-secondary), transparent); border-radius: 10px; z-index: 0; } .me-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--mercedes-primary), transparent 30%); border-radius: 10px; z-index: 0; } .me-record .record-team { color: var(--mercedes-primary); } .me-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--mercedes-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .me-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--mercedes-primary), transparent); border-radius: 10px; z-index: 0; } .me-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--general-secondary), transparent); border-radius: 10px; z-index: 0; } .ct-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, var(--custom-team-primary), transparent 30%); border-radius: 10px; z-index: 0; } .ct-record .record-team { color: var(--custom-team-primary); } .ct-back::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -5% 45%, var(--custom-team-primary), transparent 120%); border-radius: 10px; z-index: 0; border-radius: 0px; } .ct-back-normal::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--custom-team-primary), transparent); border-radius: 10px; z-index: 0; } .ct-back-normal-secondary::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -60% 45%, var(--custom-team-secondary), transparent 120%); border-radius: 10px; z-index: 0; } .generic-record::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at -19% 4%, color-mix(in srgb, var(--text-general) 60%, var(--engine-table-name)), transparent 30%); border-radius: 10px; z-index: 0; } .generic-record .record-team { color: color-mix(in srgb, var(--text-general) 60%, var(--engine-table-name)); } .extra-stats-section { display: grid; grid-auto-flow: column; grid-template-rows: repeat(2, auto); grid-auto-columns: minmax(150px, max-content); color: var(--engine-table-name); border-left: 2px solid var(--engine-table-name); padding-left: 25px; column-gap: 15px; font-size: 15px; height: 45px; } #recordsTypeDropdown{ min-width: 215px; } #tableTypeDropdown{ right: 0px; } .dropdown-submenu { position: relative; } .dropdown-global .redesigned-dropdown-menu.dropdown-submenu-menu { top: 0; left: 100%; margin-top: 0; margin-left: 6px; opacity: 0; visibility: hidden; transform: translateX(-6px); pointer-events: none; } .dropdown-global .redesigned-dropdown-menu.dropdown-submenu-menu .dropdown-submenu-menu{ margin-left: 1px; } .dropdown-global .redesigned-dropdown-menu.dropdown-submenu-menu::before { content: ''; position: absolute; top: 0; left: -6px; width: 6px; height: 100%; background: transparent; } .dropdown-submenu:hover > .redesigned-dropdown-menu.dropdown-submenu-menu { opacity: 1; visibility: visible; transform: translateX(0); pointer-events: auto; } .dropdown-submenu:not(:hover) .dropdown-submenu-menu { pointer-events: none; } .session-results-gp-menu { min-width: 240px; overflow: visible; } .session-results-scroll { max-height: 340px; overflow-y: auto; overflow-x: hidden; padding-right: 2px; } #sessionResultsGpList::-webkit-scrollbar, .news-context-textarea::-webkit-scrollbar, .news-options-context-textarea::-webkit-scrollbar { width: 4px !important; } #sessionResultsGpList::-webkit-scrollbar-thumb, .news-context-textarea::-webkit-scrollbar-thumb, .news-options-context-textarea::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px !important; } #sessionResultsGpList::-webkit-scrollbar-thumb:hover, .news-context-textarea::-webkit-scrollbar-thumb:hover, .news-options-context-textarea::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); cursor: default !important; } #sessionResultsGpList { scrollbar-width: thin; scrollbar-color: var(--white-general) transparent; } .session-results-scroll .redesigned-dropdown-item { min-width: 220px; } .session-results-session-menu { min-width: 220px; z-index: 120; } .dropdown-global .redesigned-dropdown-menu.dropdown-submenu-menu.is-open { opacity: 1; visibility: visible; transform: translateX(0); pointer-events: auto; } .free-driver:hover .bi-ban { color: var(--negative-text); } .bi-ban:hover { color: var(--negative-general); cursor: pointer; } .H2Hradio { border-bottom: none; color: var(--text-general); background-color: transparent; transition: color 0.15s, background-color 0.15s; height: 28px; font-size: 16px; padding: 1px 5px; cursor: pointer; z-index: 100; position: relative; border-radius: 5px; } .performance-graph-button { color: var(--text-general); background-color: transparent; font-size: 24px; padding: 0 3px; cursor: pointer; position: relative; transition: color 0.15s, transform 0.15s; } .performance-graph-button:hover { color: var(--text-selected); transform: scale(1.1); } .performance-graph-button i.active { color: var(--new-primary); } .GraphButton { border-bottom: none; color: var(--text-general); background-color: transparent; transition: color 0.15s, background-color 0.15s; height: 28px; font-size: 16px; padding: 0 3px; cursor: pointer; z-index: 100; position: relative; border-radius: 5px; padding: 1px 5px; } .buttons-drivers-modal { display: flex; gap: 4px; } .H2Hradio:hover { background-color: var(--new-primary-transparent); } .GraphButton:hover { background-color: var(--new-secondary-transparent); } .H2Hradio.activated { background-color: var(--new-primary-transparent); color: var(--text-selected); } .GraphButton.activated { background-color: var(--new-secondary-transparent); color: var(--text-selected-secondary); } .H2Hradio:active, .GraphButton:active { transform: scale(0.95); } .no-pointer { pointer-events: none; } .modal-dialog-lg { min-width: 1300px !important; } .modal-dialog-med { min-width: 900px !important; } .modal-dialog-sm { min-width: 600px !important; } .predict-footer { display: flex !important; justify-content: space-between !important; } .custom-bar { width: 350px; --bs-progress-bar-bg: var(--new-primary) !important; } .bar-and-indicator { opacity: 0; display: flex; align-items: center; gap: 6px; } .indicator { color: var(--new-primary); } .modal-driver { background-color: var(--elements); border-radius: 10px; padding: 8px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 15px; display: flex; justify-content: space-between; align-items: center; box-shadow: var(--shadow-s); } .modal-team { background-color: var(--elements); border-radius: 10px; padding: 2px 8px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; position: relative; font-size: 15px; display: flex; justify-content: space-between; align-items: center; box-shadow: var(--shadow-s); } .free-driver .custom-icon { color: transparent; transition: opacity 0.15s; width: 80px; opacity: 0; z-index: 3; display: flex; position: absolute; right: 6px; top: 6px; justify-content: flex-end; } .custom-icon i { transition: transform 0.15s, color 0.15s; } .standardlogo { width: 30px; height: 30px; } .custom-icon .bi-pencil-square { transition: color 0.15s, transform 0.15s; } .free-driver .bi-ban { opacity: 1 !important; transition: color 0.15s; color: var(--white-general); z-index: 10; position: absolute; right: 8px; cursor: pointer; } .free-driver .bi-ban:hover { color: var(--negative-general); } .custom-icon i:hover { color: var(--white-general); cursor: pointer; } .info-titles { color: var(--dark-text); font-size: 22px; cursor: pointer; transition: color 0.15s; } .info-titles:hover { color: var(--new-primary); } .free-driver:hover .custom-icon { color: var(--text-general); opacity: 1; } #free-drivers .free-driver:hover .custom-icon i, #free-staff .free-driver:hover .custom-icon i { color: var(--text-general); } #free-drivers .free-driver:hover .custom-icon i:hover, #free-staff .free-driver:hover .custom-icon i:hover { transform: scale(1.05); } #free-drivers, #free-staff { overflow-y: scroll; margin-right: 4px; margin-left: 0px; } #allDrivers { box-shadow: var(--shadow-s); } #free-drivers { box-shadow: none; } #free-staff { box-shadow: none; } .free-driver:hover { cursor: grab !important; background-color: var(--table-hover); } .modal-driver:hover, .modal-team:hover { cursor: default; } .modal-team.ferrari:hover { color: var(--ferrari-primary); } .modal-team.redbull:hover { color: var(--redbull-primary); } .modal-team.mclaren:hover { color: var(--mclaren-primary); } .modal-team.merc:hover { color: var(--mercedes-primary); } .modal-team.alpine:hover { color: var(--alpine-primary); } .modal-team.williams:hover { color: var(--williams-primary); } .modal-team.haas:hover { color: var(--haas-primary); } .modal-team.alpha:hover { color: var(--alphatauri-primary); } .modal-team.alfa:hover { color: var(--alfa-primary); } .modal-team.aston:hover { color: var(--aston-primary); } .blocking-div { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; display: flex; justify-content: center; align-items: center; color: var(--white-general); flex-direction: column; justify-content: center; } .drop-area.drag-over { border: 2px dashed var(--new-primary); background-color: color-mix(in srgb, var(--new-primary) 5%, var(--elements)); } .splash-text { color: var(--dark-text); display: flex; justify-content: center; } .splash-logo { height: 370px; margin-bottom: 30px; } .blocking-confidence { background-color: var(--blur-color); z-index: 100; display: flex; color: var(--white-general); backdrop-filter: blur(3px); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); width: 20%; height: 7%; position: absolute; border-radius: 10px; text-align: center; align-items: center; justify-content: center; } .blocking-h2h { background-color: var(--blur-color); z-index: 100; display: flex; justify-content: center; align-items: center; color: var(--white-general); backdrop-filter: blur(3px); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); width: 24.9%; height: 658px; position: absolute; border-radius: 10px; } .normal-driver { background-color: var(--elements); cursor: pointer; border-radius: 10px; padding: 10px; margin: 10px 7px; color: var(--text-general); transition: border-color 0.15s, color 0.15s, background-color 0.15s; display: flex; justify-content: space-between; position: relative; box-shadow: var(--shadow-s); } .one-stat-panel, .one-stat-invisible { border-radius: 10px; color: var(--text-general); transition: border-color 0.15s, background-color 0.15s; min-height: 70px; display: flex; justify-content: space-around; align-items: center; max-height: 70px; flex-direction: column; flex: 1 1 0; } .one-stat-invisible { pointer-events: none; opacity: 0; } .one-stat-panel .custom-input-number { width: 3ch; padding: 0; } .super-license-holder { display: flex !important; font-size: 16px; flex-direction: column; align-items: flex-start; justify-content: center; padding-left: 0px !important; margin-bottom: 0px !important; } .super-license-holder .form-check { margin-bottom: 0px !important; } .name-and-stat { display: flex; flex-direction: row; justify-content: space-between; align-items: center; width: 100%; height: 20px !important; } .name-and-stat.mentality { padding: 4px 0; } .bar-container { height: 100%; width: 100%; align-items: center; display: flex; } .one-stat-bar { width: 100%; height: 6px; border-radius: 1px; background-color: var(--background); position: relative; } .one-stat-progress { position: absolute; background-color: var(--new-primary); transition: width 0.15s; border-radius: 1px; height: 100%; } .bar-container:has(.mentality-level-indicator.comparison-bar) { flex-direction: row-reverse; } .stat-number { display: flex; align-items: center; } .one-stat-panel span { color: var(--text-general); transition: color 0.15s; padding-top: 4px; } .positive { color: var(--positive-general); } .negative { color: var(--negative-general); } .input-and-buttons { display: flex; flex-direction: row; align-items: center; margin-right: 5px; gap: 10px; } .team-viewer .input-buttons { flex-direction: row-reverse; } .input-buttons { display: flex; flex-direction: column; font-size: 16px !important; padding-top: 2px; gap: 5px; } .input-buttons .bi-chevron-up, .input-buttons .bi-chevron-down { font-size: 14px !important; } .team-menu-logo { width: 50px; height: 42px; display: flex; align-items: center; } .team-menu-junior-generic { width: 50px; height: 42px; object-fit: contain; } .junior-driver-row { display: flex; justify-content: flex-start; align-items: center; padding: 4px 0; } #juniorPosInTeam{ width: 30px !important; } .junior-driver-left { display: flex; gap: 10px; align-items: baseline; } .junior-driver-car { color: var(--text-general); opacity: 0.75; font-size: 12px; min-width: 52px; } .junior-driver-name { color: var(--text-general); } .junior-replacing-tag { color: var(--new-primary); font-weight: 700; } .team-menu-aston { width: 50px; height: 45px; padding-bottom: 5px; padding-left: 2px; padding-top: 5px; padding-right: 3px; } .team-menu-audi { width: 45px; height: 45px; padding-bottom: 16px; padding-left: 3px; padding-top: 17px; } .changable-team-menu-visarb { height: 45px !important; padding-bottom: 7px !important; padding-left: 6px !important; padding-top: 7px !important; } .team-menu-haas { width: 40px; height: 40px; padding-bottom: 5px; padding-top: 7px; padding-left: 6px; } .team-menu-custom { width: 50px; height: 40px; padding-bottom: 1px; padding-top: 1px; padding-left: 3px; } .changable-team-menu-williams { width: 45px; height: 45px; padding-bottom: 11px; padding-left: 7px; padding-top: 10px; padding-right: 4px; -webkit-mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .changable-team-menu-bmw { width: 100%; padding-left: 5px; } .changable-team-menu-brawn { width: 50px !important; height: 45px !important; padding-bottom: 18px !important; padding-left: 5px !important; padding-top: 18px !important; padding-right: 5px !important; margin-left: 0px !important; } .changable-team-menu-andretti { width: 100% !important; height: 100% !important; padding-bottom: 3px !important; padding-top: 8px !important; padding-left: 7px !important; } .team-menu-merc { width: 50px; height: 40px; padding-bottom: 5px; padding-top: 7px; padding-left: 10px; padding-right: 8px; } .team-menu-redbull { width: 45px; height: 40px; padding-bottom: 8px; padding-left: 3px; padding-top: 7px; } .changable-team-menu-redbull { width: 45px; height: 40px; padding-bottom: 8px; padding-left: 3px; padding-top: 7px; } .changable-team-menu-ford { width: 45px; height: 40px; padding-bottom: 6px; padding-left: 3px; padding-top: 6px; } .changable-team-menu-aston { width: 50px; height: 45px; padding-bottom: 5px; padding-left: 2px; padding-top: 5px; padding-right: 3px; } .changable-team-menu-racingpoint, .changable-team-menu-jordan { width: 45px; height: 40px; padding-bottom: 6px; padding-left: 3px; padding-top: 6px; } .changable-team-menu-cadillac { width: 45px; height: 40px; padding-bottom: 6px; padding-left: 3px; padding-top: 6px; } .team-menu-mclaren { width: 40px; height: 40px; padding-bottom: 4px; padding-top: 7px; padding-left: 6px; -webkit-mask: url('/assets/images/logos/mclaren.svg') no-repeat center; mask: url('/assets/images/logos/mclaren.svg') no-repeat center; -webkit-mask-size: 90%; mask-size: 90%; background-color: var(--mclaren-primary); } .team-menu-ferrari { width: 50px; height: 40px; padding-bottom: 6px; padding-top: 8px; padding-left: 9px; padding-right: 9px; } .changable-team-menu-alpine { width: 100% !important; height: 100% !important; padding-bottom: 6px !important; padding-top: 9px !important; padding-left: 6px !important; } .changable-team-menu-alphatauri { width: 45px !important; height: 45px !important; padding-bottom: 12px !important; padding-left: 8px !important; padding-top: 12px !important; padding-right: 4px !important; } .changable-team-menu-alfa { width: 40px !important; height: 40px !important; padding-bottom: 3px !important; padding-top: 5px !important; padding-left: 7px !important; } .team-menu-renault { width: 45px; height: 40px; padding-bottom: 6px; padding-top: 8px; padding-left: 10px; padding-right: 5px; } .changable-team-menu-hugo { height: 45px !important; width: 50px !important; padding-bottom: 7px !important; padding-top: 7px !important; } .changable-team-menu-stake { width: 40px !important; height: 40px !important; padding-bottom: 3px !important; padding-top: 5px !important; padding-left: 7px !important; } .changable-team-menu-sauber { width: 100% !important; height: 30px !important; } .changable-team-menu-lotus { width: 100% !important; height: 100% !important; padding-bottom: 4px !important; padding-top: 4px !important; padding-left: 6px !important; } .changable-team-menu-renault { width: 100% !important; height: 100% !important; padding-bottom: 6px; padding-top: 8px; padding-left: 9px; padding-right: 9px; } .changable-team-menu-toyota { width: 50px !important; height: 45px !important; padding-bottom: 9px !important; padding-left: 5px !important; padding-top: 8px !important; padding-right: 6px !important; } .changable-team-menu-porsche { width: 45px !important; height: 40px !important; padding-bottom: 6px !important; padding-top: 8px !important; padding-left: 10px !important; padding-right: 5px !important; } #teamButton { z-index: 2; } #teamsDiv { display: flex; flex-direction: column; background-color: var(--superficials); gap: 0px; padding: 10px; justify-content: space-between; } #carsDiv{ background-color: var(--superficials); } .generalPills { color: var(--text-general) !important; border: 0px 2px solid transparent !important; transition: border-color 0.15s, background-color 0.15s, color 0.15s, width 0.15s !important; display: flex; position: relative; background: none; cursor: pointer !important; } .basic-label { cursor: pointer; z-index: 1; transition: color 0.15s, transform 0.15s; pointer-events: none; } .generalPills::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; z-index: 0; } .generalPills:hover::before { opacity: 1; } .generalPills:hover, .scriptPills:hover { color: var(--white-general) !important; } .generalPills:not(:hover)::before { transition-delay: 0s; } .generalPills.active::before { opacity: 1; transition-delay: 0s; } .generalPills.active { background-color: transparent !important; color: var(--white-general) !important; } .generalPills .pill-line { background-color: var(--new-primary); width: 0%; } .generalPills:hover .pill-line, .generalPills.active .pill-line { width: 100%; } .scriptPills { color: var(--text-general) !important; border: 2px solid transparent !important; transition: border-color 0.15s, background-color 0.15s, color 0.15s, width 0.15s !important; width: 44px; height: 40px; display: flex; position: relative; background: none; } #predictpill:hover, #predictpill:hover .pill-line, #predictpill.active .pill-line, #predictpill.active { width: 120px; } #h2hpill:hover, #h2hpill:hover .pill-line, #h2hpill.active .pill-line, #h2hpill.active { width: 150px; } #newspill:hover, #newspill:hover .pill-line, #newspill.active .pill-line, #newspill.active { width: 81px; } #viewerpill:hover, #viewerpill:hover .pill-line, #viewerpill.active .pill-line, #viewerpill.active { width: 111px; } #transferpill:hover, #transferpill:hover .pill-line, #transferpill.active .pill-line, #transferpill.active { width: 133px; } #statspill:hover, #statspill:hover .pill-line, #statspill.active .pill-line, #statspill.active { width: 138px; } #constructorspill:hover, #constructorspill:hover .pill-line, #constructorspill.active .pill-line, #constructorspill.active { width: 95px; } #calendarpill:hover, #calendarpill:hover .pill-line, #calendarpill.active .pill-line, #calendarpill.active { width: 130px; } #regulationspill:hover, #regulationspill:hover .pill-line, #regulationspill.active .pill-line, #regulationspill.active { width: 158px; } #carpill:hover, #carpill:hover .pill-line, #carpill.active .pill-line, #carpill.active { width: 171px; } #modpill:hover, #modpill:hover .pill-line, #modpill.active .pill-line, #modpill.active { width: 78px; } #specialCarIcon { width: 20px; height: 20px; bottom: 8px; -webkit-mask: url('/assets/images/car.svg') no-repeat center; mask: url('/assets/images/car.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .download-save-button { width: 44px; display: flex; align-items: center; justify-content: center; align-content: center; height: 40px; transition: all 0.15s; opacity: 1; } .download-save-controls { display: flex; align-items: center; height: 100%; } .download-save-progress { width: 160px; min-width: 160px; margin-left: 8px; opacity: 1; transition: width 0.15s, min-width 0.15s, margin-left 0.15s, opacity 0.15s; } .download-save-button.hidden { opacity: 0; pointer-events: none; } .download-save-progress.hidden { width: 0; min-width: 0; margin-left: 0; opacity: 0; pointer-events: none; } .download-save-button i { font-size: 20px; cursor: pointer; background-size: 200% 200%; transition: all 0.15s, transform 0s; display: inline-block; } .download-save-button i:hover { background: linear-gradient(to right, var(--new-primary), var(--text-general), var(--new-primary)); background-clip: text; -webkit-text-fill-color: transparent; -webkit-background-clip: text; animation: gradient-move-title 4s infinite linear; background-size: 200% 200%; transform: scale(1.1); } .download-save-button i:active { transform: scale(0.98); } #panicDownloadButton{ width: max-content; margin-top: 5px; } #amoLogo:hover { background: linear-gradient(to right, var(--amo-1), var(--amo-2), var(--amo-1)); background-size: 200% 200%; animation: gradient-move-title 5s linear infinite; transform: scale(1.1); } #amoPill { height: 100%; display: flex; align-items: center; justify-content: flex-end; position: absolute; right: 0px; background: linear-gradient(to right, transparent, #202020 50%); width: 135px; border-bottom-right-radius: 10px; } #amoLogo { height: 30px; width: 50px; margin-right: 10px; transition: transform 0.15s ease; /* Solo transiciona transform */ cursor: pointer; background: #dedde6; -webkit-mask: url('../assets/images/AMO.svg') no-repeat center; -webkit-mask-size: contain; mask: url('../assets/images/AMO.svg') no-repeat center; mask-size: contain; will-change: transform; } .pill-icon { opacity: 1; transition: opacity 0.10s; font-size: 20px; position: absolute; bottom: 5px; } .pill-label { opacity: 0; transition: opacity 0.10s; position: absolute; left: 10px; cursor: pointer; pointer-events: none; } .pill-line { width: 0px; height: 2px; transition: width 0.15s, background-color 0.15s; background-color: var(--white-general); position: absolute; bottom: 0; left: -2px; pointer-events: none; } .scriptPills:hover .pill-icon, .scriptPills.active .pill-icon { opacity: 0; } .scriptPills:hover .pill-label, .scriptPills.active .pill-label { opacity: 1; } .scriptPills:hover { width: 135px; } .scriptPills::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse 70% 150% at center bottom, var(--new-primary), transparent 70%); opacity: 0; transition: opacity 0.25s ease; transition-delay: 0.15s; pointer-events: none; } .scriptPills.active { background-color: transparent !important; color: var(--white-general) !important; } .scriptPills.active::before { opacity: 1; transition-delay: 0s; /* Asegurarse de que no haya retraso en el estado activo */ } .scriptPills .pill-line { background-color: var(--new-primary); } .ai-pills::before { background: radial-gradient(ellipse 75% 160% at center 110%, var(--mode-line-ai), transparent 70%); } .ai-pills .pill-line { background-color: var(--ai-transparent); } .edit-pills::before { background: radial-gradient(ellipse 70% 160% at center 110%, var(--mode-line-edit), transparent 70%); } .edit-pills .pill-line { background-color: var(--edit-transparent); } .view-pills::before { background: radial-gradient(ellipse 70% 160% at center 110%, var(--mode-line-view), transparent 70%); } .view-pills .pill-line { background-color: var(--view-transparent); } .scriptPills:hover::before { opacity: 1; } .scriptPills:not(:hover)::before { transition-delay: 0s; /* No delay when not hovering */ } .custom-input-number { background-color: transparent; padding: 3px; color: var(--text-general); max-width: 52px; border: 0px solid; font-size: 20px; transition: background-color 0.15s, color 0.15s, border-color 0.15s !important; text-align: center; } .old-custom-input-number { background-color: transparent; padding: 6px; margin-right: 7px; color: var(--text-general); max-width: 60px; border-bottom: 2px solid transparent; border-right: 0px solid; border-left: 0px solid; border-top: 0px solid; font-size: 20px; transition: background-color 0.15s, color 0.15s, border-color 0.15s !important; text-align: center; } #contractModal .contract-modal-input { padding: 6px; margin-right: 0; max-width: none; text-align: left; border-bottom: 2px solid transparent; border-right: 0px solid; border-left: 0px solid; border-top: 0px solid; width: 140px; font-size: 18px; } #contractModal .contract-details .unit-and-input { gap: 6px; font-size: 16px; } .contract-details i:not(.new-augment-button) { transition: transform 0.15s, color 0.15s; } .custom-input-number::selection { background-color: transparent; } .custom-input-number:hover, .old-custom-input-number:hover { border-color: var(--text-selected); } .custom-input-number:focus, .old-custom-input-number:focus { color: var(--text-selected); outline: 0px solid transparent; border-color: var(--new-primary); } input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } input[type='number'] { -moz-appearance: textfield; appearance: textfield; } .normal-driver:hover { background-color: var(--table-hover); } .normal-driver.clicked .surname { color: var(--negative-text) !important; } .normal-driver.clicked .small-ovr { color: var(--negative-text) !important; } .small-ovr { gap: 5px; display: flex; overflow: hidden; } .small-ovr span { width: 25px; } .mentality-small-ovr-positive { color: var(--positive-general); } .normal-driver.clicked .mentality-small-ovr-positive { color: var(--positive-general-clicked); } .mentality-small-ovr-negative { color: var(--negative-general); } .normal-driver.clicked .mentality-small-ovr-negative { color: var(--negative-general-clicked); } .race.selected, .normal-driver.clicked, .fe-transparent.clicked:hover, .mc-transparent.clicked:hover, .me-transparent.clicked:hover, .al-transparent.clicked:hover, .wi-transparent.clicked:hover, .ha-transparent.clicked:hover, .at-transparent.clicked:hover, .as-transparent.clicked:hover, .af-transparent.clicked:hover, .rb-transparent.clicked:hover { background: var(--text-general) !important; color: var(--negative-text) !important; } .normal-driver span, .normal-driver .bold-font { z-index: 3; position: relative; } .team-template { background-color: var(--superficials); border: none; color: var(--white-general); display: flex; flex-direction: row; position: relative; padding: 5px; box-shadow: var(--shadow-s); flex-grow: 1; border-radius: 10px; overflow: hidden; } .viewer-top { display: flex; flex-direction: row; align-items: center; } .team-performance, .car-performance { display: flex; flex-direction: column; gap: 1px; } .car-performance { gap: 0px } .performance-bar { height: 5px; width: 100%; background-color: var(--background); position: relative; margin: 4px 0; } .car .performance-bar { height: 3px; margin: 0px; margin-top: 2px; } .car .performance-bar-progress { height: 3px; } .performance-bar-progress { width: 0%; height: 5px; position: absolute; border-right: 1px solid var(--text-general); } .performance-bar-progress.rb { background-color: var(--redbull-primary); } .performance-bar-progress.fe { background-color: var(--ferrari-primary); } .performance-bar-progress.mc { background-color: var(--mclaren-primary); } .performance-bar-progress.me { background-color: var(--mercedes-primary); } .performance-bar-progress.al { background-color: var(--alpine-primary); } .performance-bar-progress.wi { background-color: var(--williams-primary); } .performance-bar-progress.ha { background-color: var(--haas-primary); } .performance-bar-progress.at { background-color: var(--alphatauri-primary); } .performance-bar-progress.as { background-color: var(--aston-primary); } .performance-bar-progress.af { background-color: var(--alfa-primary); } .performance-bar-progress.ct { background-color: var(--custom-team-primary); } .logo-and-name { display: flex; flex-direction: row; max-height: 46px; align-items: center; } .new-logo-and-name { display: flex; flex-direction: row; align-items: center; min-width: 250px; z-index: 1; } .logo-and-title { display: flex; flex-direction: row; height: 54.5px; } .car-title { display: flex; flex-direction: row; align-items: center; justify-content: space-between; font-size: 13px; height: 13.5px; padding-bottom: 4px; } .team-title { display: flex; align-items: center; font-size: 18px; justify-content: space-between; } .team-performance:hover .team-title-value { color: var(--white-general); } .team-performance.selected .team-title-value { color: var(--white-general); } .team-title-value { color: var(--dark-text); transition: color 0.15s; } .car:hover .main-car, .team:hover .main-car { border-left: 2px solid var(--white-general); } .car.selected .main-car, .team.selected .main-car { border-left: 2px solid var(--white-general); } .main-car { border-left: 2px solid var(--separator); padding-left: 7px; transition: border-color 0.15s; } .team .main-car { padding-left: 12px; } .nunmber-and-car, .number-and-team { display: flex; flex-direction: row; } .performance-number { width: 8%; text-align: center; padding-right: 7px; font-size: 16px; max-height: 16px; display: flex; align-items: center; justify-content: center; padding-top: 4px; transition: transform 0.15s; } /* move it 2 pixels to the right */ .number-and-team:hover .team-number, .number-and-team.selected .team-number { transform: translateX(2px); } .nunmber-and-car:hover .performance-number, .nunmber-and-car.selected .performance-number { transform: translateX(2px); } .team-number { width: 10%; text-align: center; font-size: 22px; display: flex; align-items: center; justify-content: center; padding-right: 9.5px; transition: transform 0.15s; } .car-performance.selected .value { color: var(--white-general); } .car-performance:hover .value { color: var(--white-general); } .car-missing-parts { color: var(--negative-general); font-size: 12px; padding-top: 2px; display: flex; align-items: center; } .car-missing-parts .bi-check-all { color: var(--positive-general); font-size: 20px; } .car-missing-parts .bi-exclamation-triangle-fill { padding-bottom: 2px; margin-left: 4px; } .car-missing-parts .value { color: var(--dark-text); transition: color 0.15s; width: 56px; display: flex; justify-content: flex-end; } .team-template .team-name { max-width: 200px; } .team-name { padding-bottom: 10px; padding-left: 10px; padding-top: 10px; overflow: hidden; white-space: nowrap; text-transform: uppercase; } .perf-title { padding-bottom: 10px; padding-top: 5px; } .team-viewer { display: flex; flex-direction: row; padding: 15px 20px; align-items: center; justify-content: space-between; } .team-separator{ width: 2px; background-color: var(--separator-light); height: 620px; } #teamBudgetInput { min-width: 130px; } #costCapInput { min-width: 150px; } .team-header { font-size: 44px; height: 55px; width: 100%; } .component-and-title { display: flex; flex-direction: column; width: 100%; gap: 5px; } .component-and-title:has(.pit-crew-details) { gap: 0px; } .viewer-component { display: flex; flex-direction: row; } .facility-component { margin-top: 20px; display: flex; flex-direction: column; } .facility, .facility-level-indicator, .mentality-level-indicator { display: flex; flex-direction: row; align-items: center; } .facility { margin-top: 5px; justify-content: space-between; position: relative; height: 52px; } .condition-container { position: absolute; bottom: 0px; display: flex; flex-direction: row; width: 100%; align-items: center; } .condition-container-bar { width: 100%; background-color: var(--background); height: 2px; margin-right: 8px; margin-top: 5px; } .condition-container-bar-progress { width: 33%; background-color: var(--positive-general); height: 2px; transition: width 0.15s; } .condition-container-buttons { display: flex; flex-direction: row; font-size: 24px; height: 30px; } .condition-container-buttons i { cursor: pointer; transition: transform 0.15s, color 0.15s; display: flex; align-items: center; } .condition-container-value { font-family: "Formula1Bold"; color: var(--positive-general); width: 60px; text-align: right; } .condition-container-buttons i:hover { transform: scale(1.05); color: var(--positive-general); } .facility-refurbish { width: 5%; font-size: 20px; display: flex; justify-content: center; } .facility-refurbish svg { width: 28px; padding-bottom: 11px; transition: color 0.15s, transform 0.15s; cursor: pointer; } .facility-refurbish svg:hover { color: var(--positive-general); transform: scale(1.1); } .facility-level-indicator, .mentality-level-indicator { gap: 6px; margin: 0 10px; padding-top: 1px; flex-grow: 1; } .facility-name { width: 50%; color: var(--text-general); } .custom-button { height: 40px; font-size: 16px; padding: 0 10px; } .color-selector { display: flex; align-items: center; gap: 5px; } .color-selector .title-secondary{ width: 150px; } .logo-selector { display: flex; flex-direction: column; gap: 5px; } .logo-and-preview { display: flex; flex-direction: row; gap: 10px; align-items: center; } .title-secondary{ color: var(--text-secondary); } .logo-preview { width: 30px; height: 30px; background-color: transparent; border: solid 1px var(--toast-background); } .color-reader { background-color: transparent; border: none; color: var(--text-general); width: 100px; } .color-picker { width: 30px; height: 30px; background-color: transparent; border: none; cursor: pointer; } .custom-logo-selection { display: flex; gap: 10px; flex-direction: row; } .colors-selector { display: flex; flex-direction: column; gap: 10px; } #teamMenu a, .team-select-menu a { display: flex; align-items: center; justify-content: flex-start; padding: 0; } #teamContractMenu { max-height: 250px; overflow-y: scroll; } #teamContractMenu a { display: flex; align-items: center; justify-content: flex-start; padding: 0; min-width: 215px; } .team-select-menu a { display: flex; align-items: center; justify-content: flex-start; padding: 0; } #viewer-first { width: 49%; padding-right: 20px; display: flex; flex-direction: column; gap: 10px; } #viewer-second { width: 50%; padding-left: 20px; align-self: flex-start; } .separator { width: 2px; background-color: var(--separator); height: 100%; } .separator-2 { margin-top: 30px; width: 2px; background-color: var(--elements); height: 90%; } .main-second-viewer { min-height: 580px; position: relative; overflow-y: auto; max-height: 620px; overflow-x: hidden; padding-right: 13px; } .save-button-zone { display: flex; flex-direction: row; justify-content: flex-end; width: 100% } .level { height: 8px; width: 20%; background-color: var(--mid-gray-contrast); transition: background-color 0.15s; } .mentality-level { height: 8px; border-radius: 2px; width: 20%; background-color: var(--mid-gray-contrast); transition: background-color 0.15s; } .mentality-and-emoji.demoralized { color: var(--mentality-demoralized); } .mentality-level.demoralized { background-color: var(--mentality-demoralized); } .mentality-and-emoji.negative { color: var(--mentality-negative); } .mentality-level.negative { background-color: var(--mentality-negative); } .mentality-and-emoji.positive { color: var(--mentality-positive); } .mentality-level.positive { background-color: var(--mentality-positive); } .mentality-and-emoji.enthusiastic { color: var(--mentality-enthusiastic); } .mentality-and-emoji.neutral { color: var(--mentality-neutral); } .mentality-level.neutral { background-color: var(--mentality-neutral); } .mentality-level.enthusiastic { background-color: var(--mentality-enthusiastic); } .bar-container i { transition: color 0.15s; cursor: pointer; } .name-and-emoji { transition: color 0.15s; } .level.activated { background-color: var(--white-general) !important; } .viewer-component .custom-input-number, .facility-component .custom-input-number, .facility-component-lg .custom-input-number { background-color: transparent !important; text-align: center; padding: 8px; height: 39.2px !important; } .facility .bi-chevron-right { margin-right: 15px; } #confidenceInput { min-width: 70px !important; } .condition-input { max-width: 80px; } .facility-condition { margin-left: 45px; gap: 10px; display: flex; flex-direction: row; align-items: center; } #longTermInput { min-width: 70px; } #objectiveButton { max-width: 290px !important; } #objectiveButton::before, #carDevButton::before, #operationButton::before, #staffButton::before, #engineButton::before { z-index: 0 !important; } .pit-crew-details .one-stat-panel { width: 33%; margin: 5px 0; /* background-color: transparent; box-shadow: none; */ } .pit-crew-details .one-stat-panel input { font-size: 14px; padding: 5px 0; width: 51px; text-align: center; } .component-title { font-size: 20px; color: var(--text-secondary); } .first-component, .second-component { display: flex; flex-direction: column; justify-content: flex-start; gap: 3px; } .first-component{ width: 35%; } .second-component{ flex-grow: 1; } #editorPill { width: 81px; } #gamePill { width: 78px; } .q-label { background-color: red; color: var(--white-general); border-radius: 5px; padding: 3px; } .component-subtitle { font-size: 20px; color: var(--text-secondary); display: flex; flex-direction: row; justify-content: space-between; align-items: center; } .logo-margin-top { margin-top: 2px; } .redbulllogo, #rblogo { width: 40px; height: 40px; padding: 8px 0; } .ferrarilogo, #felogo { width: 40px; height: 40px; padding: 5px; } .porschelogo { width: 40px; height: 40px; padding: 5px; } .andrettilogo { width: 100%; height: 100%; padding: 6px 4px; } .merclogo, #melogo { width: 40px; height: 40px; padding-bottom: 7px; padding-top: 5px; padding-left: 5px; padding-right: 4px; } .merclogo2 { width: 40px; height: 40px; padding: 5px; } .renaultlogo, #relogo { width: 100%; height: 100%; padding-bottom: 7px; padding-top: 5px; padding-left: 3px; padding-right: 2px; } #teamContractMenu .alpine-logo-container { width: 50px; } #teamContractMenu .alphatauri-logo-container { width: 50px; padding: 4px; } #teamMenu .alphatauri-logo-container:has(.alphataurilogo) { width: 50px; height: 40px; padding: 0px 0px 0px 6px; } .alphatauri-logo-container:has(.alphataurilogo) { width: 40px; height: 40px; padding: 8px 4px; } #teamMenu .alphatauri-logo-container:has(.visarblogo) { width: 50px; height: 40px; padding: 0px 4px 0px 6px; } .alphatauri-logo-container:has(.visarblogo) { width: 40px; height: 40px; padding: 0px 2px; } .alphatauri-logo-container:has(.brawnlogo) { width: 40px; height: 40px; padding-left: 4px; } #teamMenu .alphatauri-logo-container:has(.hugologo) { width: 50px; height: 40px; padding: 5px 0px 5px 2px; } .alphatauri-logo-container:has(.hugologo) { width: 40px; height: 40px; padding: 5px 0px; } #teamMenu .alpine-logo-container:has(.alpinelogo) { width: 50px; height: 40px; padding: 8px 2px 9px 7px; } .alpine-logo-container:has(.alpinelogo) { width: 40px; height: 40px; padding: 8px 2px 9px 2px; } #teamMenu .alpine-logo-container:has(.andrettilogo) { width: 50px; height: 40px; padding: 6px 2px 6px 7px; } .alpine-logo-container:has(.andrettilogo) { width: 40px; height: 40px; padding: 6px 4px; } #teamMenu .alpine-logo-container:has(.renaultlogo) { width: 50px; height: 40px; padding: 5px 0px 7px 5px; } .alpine-logo-container:has(.renaultlogo) { width: 40px; height: 40px; padding: 5px 2px 7px 3px; } #teamMenu .alpine-logo-container:has(.lotuslogo) { width: 50px; height: 40px; padding: 1px 0px 1px 5px; } .alpine-logo-container:has(.lotuslogo) { width: 40px; height: 40px; padding: 1px 0px; } .alpinelogo, #allogo { width: 100%; height: 100%; padding-bottom: 9px; padding-top: 8px; padding-left: 2px; padding-right: 2px; transition: opacity 0.15s; } .alpinelogo { -webkit-mask: url('/assets/images/logos/alpine.svg') no-repeat center; mask: url('/assets/images/logos/alpine.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .renaultlogo { -webkit-mask: url('/assets/images/logos/renault.svg') no-repeat center; mask: url('/assets/images/logos/renault.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .andrettilogo { -webkit-mask: url('/assets/images/logos/andretti.svg') no-repeat center; mask: url('/assets/images/logos/andretti.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .lotuslogo { -webkit-mask: url('/assets/images/logos/lotus.svg') no-repeat center; mask: url('/assets/images/logos/lotus.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .mclarenlogo, #mclogo { width: 40px; height: 40px; padding: 2px; -webkit-mask: url('/assets/images/logos/mclaren.svg') no-repeat center; mask: url('/assets/images/logos/mclaren.svg') no-repeat center; -webkit-mask-size: 90%; background-color: var(--mclaren-primary); transition: opacity 0.15s; mask-size: 90%; } .alfalogo, #aflogo { width: 40px; height: 40px; padding: 3px; } .team-menu-logo .astonlogo, .team-menu-logo .redbulllogo, .team-menu-logo .mclarenlogo { width: 50px; } .astonlogo, #aslogo { width: 40px; height: 40px; padding-bottom: 5px; padding-left: 1px; padding-top: 5px; } .fordlogo, .jordanlogo, .cadillaclogo { width: 40px; height: 40px; padding: 6px 2px; } .racingpointlogo{ width: 40px; height: 40px; padding: 5px; } .brawnlogo { width: 100% !important; height: 100% !important; padding: 16px 0px; padding-left: 4px; } .brawnlogo { -webkit-mask: url('/assets/images/logos/brawn.svg') no-repeat center; mask: url('/assets/images/logos/brawn.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .audilogo { width: 40px; height: 40px; padding: 12px 2px; } .team-menu-logo .audilogo { width: 50px; } .sauberlogo { width: 40px; height: 31px; padding: 6px 5px; } .sauberlogo { -webkit-mask: url('/assets/images/logos/sauber.svg') no-repeat center; mask: url('/assets/images/logos/sauber.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--sauber-primary); } .hugologo { width: 100% !important; height: 100% !important; padding: 5px 0; } .hugologo { -webkit-mask: url('/assets/images/logos/hugoboss.svg') no-repeat center; mask: url('/assets/images/logos/hugoboss.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .visarblogo { width: 100% !important; height: 100% !important; padding: 5px 2px; } .visarblogo { -webkit-mask: url('/assets/images/logos/visarb.svg') no-repeat center; mask: url('/assets/images/logos/visarb.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); } .team-menu-logo .visarblogo { width: 46px; } .toyotalogo { width: 40px; height: 40px; padding: 7px 2px; } .toyotalogo { -webkit-mask: url('/assets/images/logos/toyota.svg') no-repeat center; mask: url('/assets/images/logos/toyota.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: #EB0A1B; /* toyota primary color, different from their background gray color */ } .haaslogo, #halogo { width: 40px; height: 40px; padding-bottom: 6px; padding-top: 4px; padding-left: 3px; padding-right: 2px; } .customlogo { width: 40px; height: 40px; } .lotuslogo { width: 100%; height: 100%; padding: 1px 0px; } .alphataurilogo, #atlogo { width: 100% !important; height: 100% !important; padding: 12px 4px; } .alphataurilogo { -webkit-mask: url('/assets/images/logos/alphatauri.svg') no-repeat center; mask: url('/assets/images/logos/alphatauri.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .williamslogo, .bmwlogo, #wilogo { width: 40px; height: 40px; padding-bottom: 9px; padding-left: 6px; padding-top: 8px; padding-right: 6px; transition: opacity 0.15s; } .team-menu-williams-replace.williamslogo{ width: 100%; height: 100%; padding: 0; } .williamslogo { -webkit-mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; mask: url('/assets/images/logos/Williams_2026_logo.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--logos-general); } .bmwlogo { padding: 1px; } .team1, .team2 { max-width: 150px; text-align: center; position: relative; min-height: 48px; display: flex; align-items: center; justify-content: center; overflow: hidden; padding: 0 4px; } .driver1-name .team1, .driver2-name .team2 { color: var(--text-general) !important; transition: color 0.15s; } .team-h2h-name-bg { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); color: color-mix(in srgb, var(--text-general) 75%, transparent); opacity: 0.5; white-space: nowrap; pointer-events: none; letter-spacing: 1.2px; font-size: 26px; text-transform: uppercase; max-width: 100%; overflow: hidden; text-overflow: ellipsis; } .team-h2h-logo { width: 40px; height: 40px; position: relative; z-index: 1; } #redbull, .rbborder-down:hover { border-bottom: 2px solid var(--redbull-primary) !important; } .rbborder-top { border-top: 3px solid var(--redbull-primary) !important; } .rbborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--redbull-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .rbactivated { background-color: var(--redbull-primary); } #ferrari, .feborder-down:hover { border-bottom: 2px solid var(--ferrari-primary) !important; } .feborder-top { border-top: 3px solid var(--ferrari-primary) !important; } .feborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--ferrari-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .feactivated { background-color: var(--ferrari-primary); } #merc, .meborder-down:hover { border-bottom: 2px solid var(--mercedes-primary) !important; } .meborder-top { border-top: 3px solid var(--mercedes-primary) !important; } .meborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--mercedes-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .meactivated { background-color: var(--mercedes-primary); } #mclaren, .mcborder-down:hover { border-bottom: 2px solid var(--mclaren-primary) !important; } .mcborder-top { border-top: 3px solid var(--mclaren-primary) !important; } .mcborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--mclaren-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .mcactivated { background-color: var(--mclaren-primary); } #alpine, .alborder-down:hover { border-bottom: 2px solid var(--alpine-primary) !important } .alborder-top { border-top: 3px solid var(--alpine-primary) !important } .alborder-down-noC:hover { border-bottom: 2px solid #2293D1 !important } .alborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--alpine-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .andrettiborder-down:hover { border-bottom: 2px solid var(--andretti-primary) !important } .renaultborder-down:hover { border-bottom: 2px solid var(--renault-primary) !important } .lotusborder-down:hover { border-bottom: 2px solid var(--lotus-primary) !important } .alactivated { background-color: var(--alpine-primary); } #alfaromeo, .afborder-down:hover { border-bottom: 2px solid var(--alfa-primary) !important } .afborder-top { border-top: 3px solid var(--alfa-primary) !important } .afborder-down-noC:hover { border-bottom: 2px solid #C92D4B !important } .afborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--alfa-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .afactivated { background-color: var(--alfa-primary); } #astonmartin, .asborder-down:hover { border-bottom: 2px solid var(--aston-primary) !important } .asborder-top { border-top: 3px solid var(--aston-primary) !important } .asactivated { background-color: var(--aston-primary); } .asborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--aston-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } #alphatauri, .atborder-down:hover, .hugoborder-down:hover { border-bottom: 2px solid var(--alphatauri-primary) !important } .atborder-top { border-top: 3px solid var(--alphatauri-primary) !important } .atborder-down-noC:hover { border-bottom: 2px solid #5E8FAA !important } .hugoborder-down:hover { border-bottom: 2px solid var(--hugo-primary) !important } .visarbborder-down:hover { border-bottom: 2px solid var(--visarb-primary) !important } .toyotaborder-down:hover { border-bottom: 2px solid var(--toyota-primary) !important } .porscheborder-down:hover { border-bottom: 2px solid var(--porsche-primary) !important } .brawnborder-down:hover { border-bottom: 2px solid var(--brawn-primary) !important } .stakeborder-down:hover { border-bottom: 2px solid var(--stake-primary) !important } .audiborder-down:hover { border-bottom: 2px solid var(--audi-primary) !important } .sauberborder-down:hover { border-bottom: 2px solid var(--sauber-primary) !important } .atactivated { background-color: var(--alphatauri-primary); } .atborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--alphatauri-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } #williams, .wiborder-down:hover { border-bottom: 2px solid var(--williams-primary) !important } .wiborder-top { border-top: 3px solid var(--williams-primary) !important } .wiborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--williams-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .wiactivated { background-color: var(--williams-primary); } #haas, .haborder-down:hover { border-bottom: 2px solid var(--haas-primary) !important } .haborder-top { border-top: 3px solid var(--haas-primary) !important } .haborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--haas-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .haactivated { background-color: var(--haas-primary); } #ctstom, .ctborder-down:hover { border-bottom: 2px solid var(--custom-team-primary) !important } .ctborder-top { border-top: 3px solid var(--custom-team-primary) !important } .ctborder::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background-color: var(--custom-team-primary); border-bottom-left-radius: 10px; border-bottom-right-radius: 10px; } .ctactivated { background-color: var(--custom-team-primary); } .rbfont { color: var(--redbull-primary); } .fefont { color: var(--ferrari-primary); } .mefont { color: var(--mercedes-primary); } .mcfont { color: var(--mclaren-primary); } .alfont { color: var(--alpine-primary); } .affont { color: var(--alfa-primary); } .asfont { color: var(--aston-primary); } .atfont { color: var(--alphatauri-primary); } .wifont { color: var(--williams-primary); } .hafont { color: var(--haas-primary); } .ctfont { color: var(--custom-team-primary); } .rbbar-primary { background-color: var(--redbull-primary); } .rbbar-secondary { background-color: var(--redbull-secondary); } .febar-primary { background-color: var(--ferrari-primary); } .febar-secondary { background-color: var(--general-secondary); } .mebar-primary { background-color: var(--mercedes-primary); } .mebar-secondary { background-color: var(--general-secondary); } .mcbar-primary { background-color: var(--mclaren-primary); } .mcbar-secondary { background-color: var(--mclaren-secondary); } .albar-primary { background-color: var(--alpine-primary); } .albar-secondary { background-color: var(--alpine-secondary); } .afbar-primary { background-color: var(--alfa-primary); } .afbar-secondary { background-color: var(--alfa-secondary); } .asbar-primary { background-color: var(--aston-primary); } .asbar-secondary { background-color: var(--aston-secondary); } .atbar-primary { background-color: var(--alphatauri-primary); } .atbar-secondary { background-color: var(--alphatauri-secondary); } .wibar-primary { background-color: var(--williams-primary); } .wibar-secondary { background-color: var(--general-secondary); } .habar-primary { background-color: var(--haas-primary); } .habar-secondary { background-color: var(--haas-secondary); } .ctbar-primary { background-color: var(--custom-team-primary); } .ctbar-secondary { background-color: var(--custom-team-secondary); } .rbborder-primary { border: 2px solid var(--redbull-primary); } .rbborder-secondary { border: 2px solid var(--redbull-secondary); } .feborder-primary { border: 2px solid var(--ferrari-primary); } .feborder-secondary { border: 2px solid var(--general-secondary); } .meborder-primary { border: 2px solid var(--mercedes-primary); } .meborder-secondary { border: 2px solid var(--general-secondary); } .mcborder-primary { border: 2px solid var(--mclaren-primary); } .mcborder-secondary { border: 2px solid var(--mclaren-secondary); } .alborder-primary { border: 2px solid var(--alpine-primary); } .alborder-secondary { border: 2px solid var(--alpine-secondary); } .afborder-primary { border: 2px solid var(--alfa-primary); } .afborder-secondary { border: 2px solid var(--alfa-secondary); } .asborder-primary { border: 2px solid var(--aston-primary); } .asborder-secondary { border: 2px solid var(--aston-secondary); } .atborder-primary { border: 2px solid var(--alphatauri-primary); } .atborder-secondary { border: 2px solid var(--alphatauri-secondary); } .wiborder-primary { border: 2px solid var(--williams-primary); } .wiborder-secondary { border: 2px solid var(--general-secondary); } .haborder-primary { border: 2px solid var(--haas-primary); } .haborder-secondary { border: 2px solid var(--haas-secondary); } .ctborder-primary { border: 2px solid var(--custom-team-primary); } .ctborder-secondary { border: 2px solid var(--custom-team-secondary); } .predict-warning { font-size: 14px; } .drivers-section, .staff-section { display: flex; overflow: hidden; width: 100%; align-items: center; } .driver-template { width: calc(50% - 5px); padding: 10px; background-color: rgba(255, 255, 255, 0.1); border-radius: 5px; } .game-changes { display: flex; flex-direction: column; gap: 25px; } /** grid of 11 columns and one row */ .managing-team-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); gap: 10px; width: 100%; height: 100%; margin-top: 5px; } .team-logo-container { display: flex; justify-content: center; align-items: center; background-color: var(--elements); border-radius: 5px; border: 2px solid var(--modal-text); transition: background-color 0.15s, border-color 0.15s; cursor: pointer; } .team-logo-container.fe:hover, .team-logo-container.fe.active { background-color: var(--ferrari-primary-transparent); border-color: var(--ferrari-primary); } .team-logo-container.rb:hover, .team-logo-container.rb.active { background-color: var(--redbull-primary-transparent); border-color: var(--redbull-primary); } .team-logo-container.me:hover, .team-logo-container.me.active { background-color: var(--mercedes-primary-transparent); border-color: var(--mercedes-primary); } .team-logo-container.mc:hover, .team-logo-container.mc.active { background-color: var(--mclaren-primary-transparent); border-color: var(--mclaren-primary); } .team-logo-container.al:hover, .team-logo-container.al.active { background-color: var(--alpine-primary-transparent); border-color: var(--alpine-primary); } .team-logo-container.af:hover, .team-logo-container.af.active { background-color: var(--alfa-primary-transparent); border-color: var(--alfa-primary); } .team-logo-container.as:hover, .team-logo-container.as.active { background-color: var(--aston-primary-transparent); border-color: var(--aston-primary); } .team-logo-container.at:hover, .team-logo-container.at.active { background-color: var(--alphatauri-primary-transparent); border-color: var(--alphatauri-primary); } .team-logo-container.wi:hover, .team-logo-container.wi.active { background-color: var(--williams-primary-transparent); border-color: var(--williams-primary); } .team-logo-container.ha:hover, .team-logo-container.ha.active { background-color: var(--haas-primary-transparent); border-color: var(--haas-primary); } .team-logo-container.ct:hover, .team-logo-container.ct.active { background-color: var(--custom-team-primary-transparent); border-color: var(--custom-team-primary); } .team-logo-container:active { transform: scale(0.96); } .team-logo-container, .team-logo-container img, .team-logo-container img.alfalogo { max-width: 50px; max-height: 50px; width: auto; height: auto; } .team-logo-container img { padding: 10px; } .team-logo-container.rb img { padding: 0px 2px; } .team-logo-container.as img { padding: 4px; } body:has(#mentalitySpan.frozen) #driverMentalityAttributes { display: none; pointer-events: none; } .mentality-difficulty { display: flex; flex-direction: row; } .mentality-changes { display: flex; flex-direction: column; gap: 5px; border-right: 2px solid var(--separator); width: 50%; } #customGearButton { margin-left: 5px; transition: transform 0.15s ease, color 0.15s; cursor: pointer; display: inline-block; } #customGearButton:hover { color: var(--text-selected); } #customGearButton.custom { color: var(--new-primary); transform: rotate(45deg); } .difficulty-changes { display: flex; flex-direction: column; gap: 5px; padding-left: 10px; width: 50%; } .mentality-selector { display: flex; flex-direction: row; align-items: center; gap: 10px; } .mentality-selector .form-switch { padding-right: 0px; } .modal-text { color: var(--modal-text); } input[type="range"] { width: 100%; outline: none; } input[type="range"]:focus, input[type="range"]:focus-visible { outline: none; box-shadow: none; } datalist { display: flex; justify-content: space-between; width: 100%; padding: 0; margin-top: -27px; pointer-events: none; } datalist .my-mark { border-right: 2px solid var(--separator); height: 10px; width: 2px; } .slider-and-label{ display: flex; flex-direction: row; align-items: center; gap: 10px; width: 400px; } #turningPointsFrequencyLabel{ width: 160px; } input[type="range"]::-webkit-slider-thumb { background-color: var(--new-primary-dark); z-index: 2; box-shadow: none; } input[type="range"]:focus::-webkit-slider-thumb, input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: none; } input[type="range"]::-webkit-slider-thumb:active { background-color: var(--new-primary-dark); box-shadow: none; } input[type="range"]::-webkit-slider-thumb:hover { outline: 1px solid var(--new-primary); transform: scale(1.05); } input[type="range"]::-webkit-slider-runnable-track { background-color: var(--text-selected); } input[type="range"]::-moz-range-thumb { background-color: var(--new-primary-dark); border: none; box-shadow: none; } input[type="range"]::-moz-range-thumb:active { background-color: var(--new-primary-dark); box-shadow: none; } input[type="range"]::-moz-range-track { background-color: var(--text-selected); } .option-state { padding-left: 5px; transition: color 0.15s; } #turningPointsFrequencyLabel.tp-default { color: var(--modal-text); } #turningPointsFrequencyLabel.tp-less { color: var(--new-secondary); } #turningPointsFrequencyLabel.tp-more { color: var(--new-primary); } .dif-warning { cursor: pointer; } .dif-warning:hover { transform: translateY(-1px); filter: brightness(1.2); } .dif-warning::selection { background-color: transparent; } .option-state.default, .dif-warning.default { color: var(--new-secondary); } body.og-theme .option-state.default, body.og-theme .dif-warning.default { color: var(--new-primary); } body.og-theme .option-state.fixed { color: var(--new-secondary); } .option-state.frozen, .dif-warning.frozen { color: var(--frozen) } .option-state.extra-hard, span.extra-hard, .dif-warning.extra-hard, .dif-status.extra-hard { color: var(--extra-hard) } .option-state.brutal, span.brutal, .dif-warning.brutal, .dif-status.brutal { color: var(--brutal) } .option-state.unfair, span.unfair, .dif-warning.unfair, .dif-status.unfair { color: var(--unfair) } .option-state.insane, span.insane, .dif-warning.insane, .dif-status.insane { color: var(--insane) } .option-state.impossible, span.impossible, .dif-warning.impossible, .dif-status.impossible { color: var(--impossible) } .option-state.fixed, span.fixed, .dif-warning.fixed, #lightDif.impossible, #lightDif~.dif-status.impossible { color: var(--new-primary) !important; } .option-state.reduced-weight, span.reduced-weight, .dif-warning.reduced-weight { color: var(--new-primary) } .option-state.custom { color: var(--text-general); } #refurbishTitle { padding-top: 10px; border-top: 2px solid var(--separator); margin-right: 32px; } .difficulty-warnings { font-size: 12px; display: flex; flex-wrap: wrap; margin-top: -7px; gap: 2px 7px; height: 58px; align-content: flex-start; } .dif-warning:not(.default) { cursor: pointer; transition: color 0.15s, opacity 0.1s, transform 0.15s, filter 0.15s; } .dif-warning.disabled, #lightDif.disabled { text-decoration: line-through; color: var(--text-general); opacity: 0.63; } .modal-warning { background-color: var(--modal-warning); color: var(--modal-warning-text); border-radius: 10px; padding: 5px; } .name-and-arrows { display: flex; flex-direction: row-reverse; align-items: center; } .part-performance-title .arrows { display: flex; flex-direction: row-reverse; align-items: center; gap: 3px; } .part-performance-title .arrows i { font-size: 14px; } #blockDiv { opacity: 1; transition: all 0.15s; } #blockDiv.disappear { opacity: 0; pointer-events: none; } @keyframes fadeOut { 0% { opacity: 1; } 100% { opacity: 0; } } .splash-box { background-color: var(--superficials); border-radius: 20px; padding: 20px 30px; min-width: 80%; min-height: 60%; display: flex; flex-direction: column; gap: 20px; box-shadow: var(--shadow-m); opacity: 0; transform: translateY(50px); z-index: 2; } body:has(#blockDiv:not(.disappear)) .footer .socials { opacity: 0; } body:has(#blockDiv:not(.disappear)){ overflow: hidden; } .socials-box { background-color: var(--superficials); border-radius: 20px; padding: 15px 10px; min-width: 80%; display: flex; flex-direction: row; align-items: center; gap: 20px; box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; opacity: 0; transform: translateY(50px); justify-content: space-around; } .socials-box a { color: var(--text); font-size: 22px; text-decoration: none; transition: color 0.15s; } @keyframes fadeIn { 0% { opacity: 0; transform: translateY(50px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes fadeAppearLeft { 0% { opacity: 0; transform: translateX(-10px); } 100% { opacity: 1; transform: translateX(0); } } @keyframes fadeInHigher { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } } .drop-area { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; border: 2px dashed var(--disabled-text); border-radius: 20px; display: flex; flex-direction: column; transition: all 0.15s; } .list-item { display: flex; flex-direction: row; font-size: 20px; } .list-item:has(.completed) .number-icon span, .list-item:not(:has(.completed)) .number-icon i { opacity: 0; rotate: 90deg; } .list-item:has(.completed) .number-icon i, .list-item:not(:has(.completed)) .number-icon span { opacity: 1; rotate: 0; } .list-item:has(.completed) .number-icon i { color: var(--positive-general); } .number-icon { position: relative; margin-right: 25px; } .number-icon span, .number-icon i { position: absolute; transition: all 0.15s; } .list-text { transition: all 0.15s; } .list-text.completed { color: var(--positive-general); } /* .icon-hidden{ opacity: 0; } */ .title-and-spinner { align-self: center; } .main-title { font-size: 52px; line-height: 1; } .glitch { color: var(--text); font-size: 52px; font-weight: 700; position: relative; margin: 0 auto; text-align: center; line-height: 1; } .glitch::before, .glitch::after { content: 'EDITOR'; position: absolute; bottom: 5px; left: 0; overflow: hidden; color: var(--text); background-color: var(--background-color); } .glitch::before { left: 2px; text-shadow: 2px 0 var(--new-primary-dark); animation: animate 3s infinite linear; width: 100%; } .glitch::after { left: -3px; text-shadow: -3px 0 var(--new-secondary-dark); animation: animate 2s infinite linear; width: 100%; } @keyframes animate { 0% { clip: rect(32px, 9999px, 29px, 0); } 4% { clip: rect(32px, 9999px, 29px, 0); } 5% { clip: rect(28px, 9999px, 20px, 0); } 6% { clip: rect(28px, 9999px, 20px, 0); } 10% { clip: rect(21px, 9999px, 23px, 0); } 14% { clip: rect(21px, 9999px, 23px, 0); } 15% { clip: rect(23px, 9999px, 7px, 0); } 16% { clip: rect(23px, 9999px, 7px, 0); } 20% { clip: rect(37px, 9999px, 34px, 0); } 24% { clip: rect(37px, 9999px, 34px, 0); } 25% { clip: rect(53px, 9999px, 58px, 0); } 26% { clip: rect(53px, 9999px, 58px, 0); } 30% { clip: rect(30px, 9999px, 44px, 0); } 34% { clip: rect(30px, 9999px, 44px, 0); } 35% { clip: rect(30px, 9999px, 19px, 0); } 36% { clip: rect(30px, 9999px, 19px, 0); } 40% { clip: rect(36px, 9999px, 39px, 0); } 44% { clip: rect(36px, 9999px, 39px, 0); } 45% { clip: rect(6px, 9999px, 47px, 0); } 46% { clip: rect(6px, 9999px, 47px, 0); } 50% { clip: rect(0px, 9999px, 33px, 0); } 54% { clip: rect(0px, 9999px, 33px, 0); } 55% { clip: rect(15px, 9999px, 33px, 0); } 56% { clip: rect(15px, 9999px, 33px, 0); } 60% { clip: rect(28px, 9999px, 19px, 0); } 64% { clip: rect(28px, 9999px, 19px, 0); } 65% { clip: rect(53px, 9999px, 58px, 0); } 66% { clip: rect(53px, 9999px, 58px, 0); } 70% { clip: rect(30px, 9999px, 44px, 0); } 74% { clip: rect(30px, 9999px, 44px, 0); } 75% { clip: rect(30px, 9999px, 19px, 0); } 76% { clip: rect(30px, 9999px, 19px, 0); } 80% { clip: rect(36px, 9999px, 39px, 0); } 84% { clip: rect(36px, 9999px, 39px, 0); } 85% { clip: rect(6px, 9999px, 47px, 0); } 86% { clip: rect(6px, 9999px, 47px, 0); } 90% { clip: rect(0px, 9999px, 33px, 0); } 94% { clip: rect(0px, 9999px, 33px, 0); } 95% { clip: rect(15px, 9999px, 33px, 0); } 96% { clip: rect(15px, 9999px, 33px, 0); } 100% { clip: rect(32px, 9999px, 29px, 0); } } body:has(#modpill.active):has(#mods2025View:not(.d-none)) { background-color: transparent !important; background-image: url('../assets/images/modback.png') !important; background-size: cover; background-position: center; background-repeat: no-repeat; filter: none; } body:has(#modpill.active):has(#mods2025View:not(.d-none)) .version-panel{ background-color: #0F1D1D; } body:has(#modpill.active):has(#mods2026View:not(.d-none)) .footer-notification{ opacity: 0; } body:has(#modpill.active):has(#mods2026View:not(.d-none)) .version-panel{ background-color: var(--background); } body:has(#modpill.active):has(#mods2026View:not(.d-none).illuminated) .version-panel{ background-color: #262626; } body:has(#modpill.active) .tech-grid, body:has(#modpill.active) .glow-spot { display: none; } body:has(#modpill.active):has(#mods2026View:not(.d-none):not(.illuminated)) { background-color: var(--background) !important; background-image: repeating-linear-gradient(115deg, color-mix(in srgb, gray 9%, transparent) 0 1px, transparent 1px 20px), repeating-linear-gradient(115deg, color-mix(in srgb, gray 8%, transparent) 0 1px, transparent 1px 22px), repeating-linear-gradient(115deg, color-mix(in srgb, gray 9%, transparent) 0 1px, transparent 1px 20px) !important; background-size: cover, cover, cover, 33.333% 100%, 33.333% 100%, 33.333% 100%; background-position: center, center, center, left top, center top, right top; background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat; filter: none; overflow: hidden !important; } body:has(#modpill.active):has(#mods2026View:not(.d-none)) { background-color: var(--background) !important; background-image: repeating-linear-gradient(115deg, color-mix(in srgb, var(--ferrari-primary) 9%, transparent) 0 1px, transparent 1px 20px), repeating-linear-gradient(115deg, color-mix(in srgb, var(--ferrari-primary) 8%, transparent) 0 1px, transparent 1px 22px), repeating-linear-gradient(115deg, color-mix(in srgb, var(--ferrari-primary) 9%, transparent) 0 1px, transparent 1px 20px) !important; background-size: cover, cover, cover, 33.333% 100%, 33.333% 100%, 33.333% 100%; background-position: center, center, center, left top, center top, right top; background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat; filter: none; overflow: hidden !important; } body:has(#modpill.active):has(#mods2026View:not(.d-none)) .scroll-wrapper{ height: 100%; } .aduo-tp-toggle{ position: absolute; right: 0; opacity: 0; top: -35px; padding: 0; } .apply-all-button{ position: absolute; left: 0; opacity: 0; top: -40px; } .apply-all-button button{ display: flex; align-items: center; gap: 5px; } .apply-all-button button i{ opacity: 1 !important; transform: rotate(270deg); } .apply-all-button button span{ padding-right: 5px !important; } .button-with-icon.apply-all-2026.applied{ background-color: var(--positive-transparent); color: var(--positive-general); } body:has(#modpill.active) .apply-all-button, body:has(#modpill.active) .aduo-tp-toggle { animation: change-appear 0.6s ease-out forwards; animation-delay: calc(0.8s + var(--order) * 0.1s); } .mod-banner { display: flex; flex-direction: row; gap: 60px; align-items: center; justify-content: center; width: 100%; padding: 60px 0 60px 0; scale: 1.1; } .mod-name img { width: 600px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; transform: translateY(-40px); opacity: 0; will-change: opacity, transform, filter; } body:has(#modpill.active) .mods-year-view:not(.d-none) .mod-name img:not(.title-2026) { animation: logo-appear 0.6s forwards; } body:has(#modpill.active) .mods-year-view:not(.d-none) .mod-name img.title-2026 { animation: logo-appear-blur 0.75s forwards; } @keyframes logo-appear { 0% { opacity: 0; transform: translateY(-40px); } 100% { opacity: 1; transform: translateY(0); } } @keyframes logo-appear-blur { 0% { opacity: 0; transform: translateY(10px); filter: blur(12px); } 100% { opacity: 1; transform: translateY(0); filter: blur(0); } } @media (prefers-reduced-motion: reduce) { body:has(#modpill.active) .mods-year-view:not(.d-none) .mod-name img { animation: none !important; opacity: 1; transform: translateY(0); filter: none; } } .mod-drivers { display: flex; flex-direction: row; align-items: flex-end; position: relative; overflow-x: clip; } .small-rectangle { width: 127px; height: 116px; border-top-left-radius: 10px; border-top-right-radius: 10px; position: relative; opacity: 0; will-change: opacity; transform: translateY(20px); overflow-x: clip; } .small-rectangle img, .high-rectangle img { width: 130px; position: absolute; bottom: 0; left: 0; opacity: 0; z-index: 20; transform: translateY(20px); } .high-rectangle { width: 130px; height: 173px; border-top-left-radius: 10px; border-top-right-radius: 10px; position: relative; opacity: 0; overflow-x: clip; } .ver-lines { position: absolute; top: 0; left: 0; width: 101.5%; height: 100%; background-color: #020988; mask-image: url('../assets/images/ver-lines.svg'); mask-size: cover; mask-position: center; mask-repeat: no-repeat; z-index: -1; border-top-left-radius: 10px; border-top-right-radius: 10px; } .ant-lines { position: absolute; top: 0; left: 0; width: 101.5%; height: 100%; mask-image: url('../assets/images/ant-lines.svg'); mask-size: cover; mask-position: center; mask-repeat: no-repeat; z-index: -1; opacity: 1; border-top-left-radius: 10px; border-top-right-radius: 10px; background-color: #29e1c6; } .ham-lines { position: absolute; top: 0; left: 0; width: 101.5%; height: 100%; mask-image: url('../assets/images/ham-lines.svg'); mask-size: cover; mask-position: center; mask-repeat: no-repeat; z-index: -1; opacity: 1; border-top-left-radius: 10px; border-top-right-radius: 10px; background-color: #bb0e0f; } body:has(#modpill.active) .small-rectangle, body:has(#modpill.active) .high-rectangle, body:has(#modpill.active) .small-rectangle img, body:has(#modpill.active) .high-rectangle img { animation: rectangle-appear 0.5s forwards; animation-delay: calc(var(--order) * 0.08s); } @keyframes rectangle-appear { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } } body:has(#modpill.active) .mod-drivers .mv-gradient, body:has(#modpill.active) .mod-drivers .lh-gradient, body:has(#modpill.active) .mod-drivers .ka-gradient { animation: gradient-appear 0.6s forwards; animation-delay: calc(var(--order) * 0.08s); } @keyframes gradient-appear { 0% { opacity: 0; } 100% { opacity: 0.7; } } .ka-rectangle { background: linear-gradient(to top, #41ffdb, #008e7f); } .ka-rectangle img { left: -2px; bottom: -1px; } .lh-rectangle img { left: 1px; } .ka-gradient { content: ''; position: absolute; top: -1px; left: 82px; width: 107%; height: 126px; background: radial-gradient(circle at center bottom, #41ffdb 6%, transparent 51%); z-index: -1; opacity: 0; } .lh-gradient { content: ''; position: absolute; top: -1px; right: 82px; width: 107%; height: 126px; background: radial-gradient(circle at center bottom, #ff1500 6%, transparent 51%); z-index: -1; opacity: 0; } .mv-gradient { content: ''; position: absolute; top: -43px; right: 0%; width: 100%; height: 112px; background: radial-gradient(circle at center bottom, #0014aa 14%, transparent 51%); z-index: -1; opacity: 0; } .mv-rectangle { background: linear-gradient(to top, #0014aa, #06093a); } .lh-rectangle { background: linear-gradient(to top, #ff1500, #530000); } .mod-name { display: flex; flex-direction: column; align-items: center; transform: translateY(-20px); } .mod-subtitle { display: inline-flex; align-items: center; justify-content: center; font-size: 22px; gap: 0.75em; text-transform: uppercase; color: #ffffff; transform: translateY(-20px); white-space: nowrap; margin-top: -20px; } .mod-subtitle-letter { display: inline-block; opacity: 0; text-shadow: none; font-family: 'Formula1Dark'; will-change: opacity, text-shadow; padding-top: 3px; } .mod-subtitle-letter.number{ font-family: 'NumbersFont'; } .mod-subtitle-letter.number + .mod-subtitle-letter.number { margin-left: -0.12em; } .mod-subtitle-space { display: inline-block; width: 0.6ch; } body:has(#modpill.active) #mods2026View:not(.d-none) .mod-subtitle-letter { animation: mod-subtitle-letter-appear 0.9s forwards; animation-delay: calc(0.15s + (var(--order) * 0.06s)); } .mod-creators { display: flex; flex-direction: row; gap: 10px; align-items: center; justify-content: center; } .mod-creators a { text-transform: uppercase; font-size: 14px; color: #dedde6; text-shadow: none; text-decoration: none; cursor: pointer; opacity: 0; transition: color 0.15s, text-shadow 0.15s; position: relative; } .mod-creators a::before { color: white; text-shadow: 0 0 5px white; opacity: 0; position: absolute; top: 0; left: 0; width: 100%; z-index: 2; } .mod-creators a.littleonion::before { content: 'LITTLEONION'; } .mod-creators a.bonnex::before { content: 'BONNEX'; } .mod-creators a.grzelu::before { content: 'GRZELU'; } .mod-creators a.thatgingerdude::before { content: 'THATGINGERDUDE'; } .mod-creators a.n4x::before { content: 'N4X'; } .mod-creators a.nexus::before { content: 'THEFIR3NEXUS'; } .mod-creators a.w1echuz::before { content: 'W1ECHUZ'; } .mod-creators a.cody::before { content: 'CMDESIGNS'; } .mod-creators a.murillo::before { content: 'PROJECT 212'; } .mod-creators a.kevintaddo::before { content: 'KEVINTADDO'; } .mod-creators a.lucas::before { content: 'LCSKPMN'; } .mod-creators a:hover { color: white; text-shadow: 0 0 5px white; } .download-links{ display: flex; flex-direction: row; align-items: center; justify-content: center; gap: 10px; } .recommended-downloads{ transform: translateY(25px); color: var(--text-general); font-size: 17px; display: flex; flex-direction: column; align-items: center; } .grid-and-downloads{ display: flex; flex-direction: column; /* width: 50%; */ min-width: 63%; margin-top: 100px; z-index: 1; } #mods2026View{ flex-direction: row; padding: 0 26px; height: 100%; align-items: flex-start; } #mods2026View .changes-grid{ grid-template-columns: repeat(3, 1fr); } .mods-2026-blocking{ width: 100%; text-align: center; margin-top: unset; margin: auto; padding: 0 7%; transform: translateY(-30px); } .mods-2026-banner{ scale: 1; width: 100%; flex-direction: column; padding: 0; height: 100%; z-index: 2; } #mods2026View .one-change{ box-shadow: none; transition: box-shadow 0.15s; } #mods2026View.illuminated .one-change{ box-shadow: var(--shadow-s); } #mods2026View.illuminated .glow-2026{ opacity: 0.2; } #mods2026View.illuminated .driver-and-rectangle, #mods2026View.illuminated .mod-subtitle{ filter: grayscale(0) !important; } .driver-and-rectangle{ scale: 1.7; opacity: 1; transform: translateY(21px); filter: grayscale(1); transition: filter 0.15s; } @keyframes rectangle-2026-appear { 0% { opacity: 0; transform: translateY(20px); filter: blur(10px); } 100% { opacity: 1; transform: translateY(0); filter: blur(0); } } @keyframes driver-appear { 0% { opacity: 0; transform: translateX(-50%) translateY(20px); filter: blur(10px); } 100% { opacity: 1; transform: translateX(-50%) translateY(0); filter: blur(0); } } .rectangle-2026{ width: 295px; height: 340px; border-top-left-radius: 40px; border-top-right-radius: 40px; position: relative; opacity: 0; will-change: opacity; /* transform: translateY(20px); */ background: linear-gradient(to top, #F43627, #58120d); overflow: clip; animation: rectangle-2026-appear 0.5s forwards; } .glow-2026{ position: absolute; bottom: 0; left: 80%; transform: translateX(-50%); width: 100%; height: 100%; background: radial-gradient(circle at bottom, white 0%, transparent 48%); z-index: 0; opacity: 0; transition: opacity 0.15s; } .driver-2026{ height: 485px; position: absolute; left: 50%; bottom: 0; opacity: 0; transform: translateX(-50%) translateY(0px); /* opacity: 0; */ z-index: 20; animation: driver-appear 0.6s forwards 0.2s; } .lines-2026{ position: absolute; top: 0; left: 0; width: 101.5%; height: 100%; z-index: 15; transform: scaleY(1.7); } .mod-name.title-2026{ margin-bottom: 180px; margin-top: -100px; z-index: 22; } img.title-2026{ margin-top: -70px; } .title-2026 .mod-subtitle{ background: red; padding: 0 20px; transform: skewX(325deg); margin-top: -35px; font-size: 17px; filter: grayscale(1); animation: mod-subtitle-appear 0.5s forwards; transition: filter 0.15s; } @keyframes mod-subtitle-appear { 0% { opacity: 0; filter: blur(5px) grayscale(1); transform: translateY(20px) skewX(325deg); } 55% { opacity: 1; } 100% { opacity: 1; filter: blur(0px) grayscale(1); transform: translateY(0px) skewX(325deg); } } #mods2026View .mod-creators{ margin-top: 20px; max-width: 600px; flex-wrap: wrap; } .recommended-downloads-title { --order: 0; } .recommended-downloads a{ color: var(--text-general); text-decoration: none; } .recommended-downloads .download-link-text { position: relative; display: inline-block; transition: color 0.15s, text-shadow 0.15s; } .recommended-downloads .download-link-text::before { content: attr(data-glow); text-shadow: 0 0 5px white; opacity: 0; position: absolute; top: 0; left: 0; width: 100%; z-index: 2; pointer-events: none; transition: opacity 0.15s; } .recommended-downloads a:hover .download-link-text::before { opacity: 1; } body:has(#modpill.active) .recommended-downloads .recommended-downloads-title, body:has(#modpill.active) .recommended-downloads .download-link, body:has(#modpill.active) .recommended-downloads .creator-separator { opacity: 0; filter: blur(3px); transform: translateY(-10px); animation: recommended-downloads 0.6s forwards; animation-delay: calc((var(--order) * 0.07s) + 1.9s); } body:has(#modpill.active) .mod-creators a { animation: creator-appear 0.6s forwards; animation-delay: calc((var(--order) * 0.07s)); } body:has(#modpill.active) .mod-creators a::before { animation: before-appear 0.9s; animation-delay: calc((var(--order) * 0.07s)); } #season_mods { align-items: center; overflow: hidden; height: 100%; } #modSelectPanel{ z-index: 2; height: 49px; align-self: flex-start; } .mods-year-view { width: 100%; display: flex; flex-direction: column; align-items: center; margin-top: -49px; } .mods-placeholder { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; margin-top: 30px; opacity: 0.9; } .mods-2026-banner .mods-placeholder { margin-top: 0; } .mods-placeholder-title { color: #dedde6; font-size: 44px; font-family: 'NumbersFont'; } .mods-placeholder-subtitle { color: var(--text-general); font-size: 16px; } .coming-soon { display: flex; flex-direction: column; gap: 10px; opacity: 0; align-items: center; } .coming-soon span { color: #dedde6; font-size: 44px; font-family: 'NumbersFont'; } body:has(#modpill.active) .coming-soon { animation: creator-appear 0.7s forwards; animation-delay: 1.1s; } @keyframes creator-appear { 0% { opacity: 0; } 100% { opacity: 1; } } @keyframes before-appear { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } @keyframes mod-subtitle-letter-appear { 0% { opacity: 0; filter: blur(2px); transform: translateX(-7px) skewX(35deg); /* text-shadow: 0 0 10px rgba(255, 255, 255, 0.95); */ } 55% { opacity: 1; /* text-shadow: 0 0 10px rgba(255, 255, 255, 0.95); */ } 100% { opacity: 1; filter: blur(0px); transform: translateX(0) skewX(35deg); /* text-shadow: none; */ } } @keyframes recommended-downloads { 0% { opacity: 0; filter: blur(3px); transform: translateY(-10px); /* text-shadow: 0 0 10px rgba(255, 255, 255, 0.95); */ } 55% { opacity: 1; /* text-shadow: 0 0 10px rgba(255, 255, 255, 0.95); */ } 100% { opacity: 1; filter: blur(0px); transform: translateY(0); /* text-shadow: none; */ } } @media (prefers-reduced-motion: reduce) { .mod-subtitle-letter { opacity: 1; animation: none !important; } body:has(#modpill.active) .recommended-downloads .recommended-downloads-title, body:has(#modpill.active) .recommended-downloads .download-link, body:has(#modpill.active) .recommended-downloads .creator-separator { opacity: 1; filter: none; transform: none; animation: none !important; } } @keyframes separator-appear { 0% { opacity: 0; } 50% { opacity: 1; background-color: white; box-shadow: 0 0 5px white; } 100% { opacity: 1; background-color: var(--text-general); box-shadow: none; } } .creator-separator { width: 2px; background-color: var(--text-general); height: 14px; opacity: 0; } body:has(#modpill.active) .creator-separator { animation: separator-appear 0.6s forwards; animation-delay: calc((var(--order) * 0.07s)); } .status-info { display: flex; align-items: center; justify-content: center; background-color: transparent; } .loader { width: 18px; height: 18px; border: 2px solid var(--modal-text); border-bottom-color: var(--new-primary); border-radius: 50%; display: inline-block; box-sizing: border-box; animation: rotation 1s linear infinite; transition: opacity 0.1s; opacity: 1; } .loader.hidden { opacity: 0; } @keyframes rotation { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .footer .bi-exclamation-triangle-fill { color: color-mix(in srgb, var(--negative-general) 60%, var(--generals)); transition: all 0.15s; padding: 0px; } .footer .bi-exclamation-triangle-fill:hover { color: color-mix(in srgb, var(--negative-general) 95%, var(--generals)); } body.og-theme footer a.bi-custom-patreon::before { background-color: var(--slight-contrast); } a.bi-custom-patreon::before, i.bi-custom-patreon::before { content: ""; display: inline-block; width: 1em; height: 1em; -webkit-mask: url('/assets/images/patreonLogo.svg') no-repeat center; mask: url('/assets/images/patreonLogo.svg') no-repeat center; -webkit-mask-size: contain; mask-size: contain; background-color: var(--text-general); transition: background-color 0.15s; } .footer a.bi-custom-patreon::before { background-color: var(--dark-text) } a.bi-custom-patreon:hover::before { background-color: var(--support-me) !important; } a.bi-custom-patreon:hover { color: var(--support-me) !important; } a.bi-custom-patreon.open-slide-up { animation: goUp 0.4s forwards; } a.bi-custom-patreon.close-slide-up { animation: goDown 0.4s forwards; } @keyframes goUp { 0% { transform: translateY(0); } 60% { transform: translateY(5px); } 100% { transform: translateY(-30px); } } @keyframes goDown { 0% { transform: translateY(-30px); } 60% { transform: translateY(-5px); } 100% { transform: translateY(0); } } .patreon-slide-up { padding: 10px; background-color: var(--elements-hover); box-shadow: 0 0 10px var(--generals); border-radius: 10px; transform: translateY(125%); transition: transform 0.15s; position: fixed; bottom: 42px; left: 10px; color: var(--text-general); z-index: 190; max-width: 600px; } .patreon-slide-up.open { transform: translateY(0); } .slide-up-title-and-x { margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; width: 100%; font-size: 20px; height: 30px; } #patreonSlideUpClose { font-size: 26px; cursor: pointer; color: var(--modal-text); transition: all 0.15s; } #patreonSlideUpClose:hover { color: var(--negative-general); } .patreon-unlockables { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; padding-top: 5px; border-top: 2px solid var(--separator); } #modelSelectorButton { margin-top: 0px !important; } #aiModelmenu .dropdown-item { display: flex; flex-direction: row; align-items: flex-start; justify-content: space-between; } #aiModelmenu .dropdown-item i { font-size: 22px; height: 22px; width: 22px; display: flex; align-items: center; justify-content: center; pointer-events: none; transition: transform 0.15s, opacity 0.15s; } .model-and-description { display: flex; flex-direction: column; gap: 3px; pointer-events: none; } .model-and-description .model-name { color: var(--text-general); font-family: "Formula1Bold"; pointer-events: none; } .model-and-description .model-description { font-family: "Formula1"; color: var(--text-secondary); pointer-events: none; font-size: 14px; } .patreon-themes, .ai-model-settings { display: flex; flex-direction: column; gap: 5px; } .patreon-unlockables .modal-subtitle { font-size: 18px; line-height: 1.3; } .api-key-section { display: flex; flex-direction: column; gap: 5px; } #patreonChanges>.api-key-section { margin-top: 10px; padding-top: 5px; border-top: 2px solid var(--separator); } #news .modal-text { position: absolute; top: 350%; left: 50%; transform: translate(-50%, -50%); font-size: 20px; text-align: center; } .modal-text a, .news-error-api-key a { color: var(--new-primary); transition: color 0.15s; } .modal-text a:hover { color: var(--new-primary-dark); } .user-name-and-logout { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; } #userButton, #userButton:hover, #userButton:hover i, #userButton:hover i::before { color: var(--new-primary); background-color: transparent; cursor: default; } #patreonLogoutButton:hover { color: var(--negative-general); background-color: color-mix(in srgb, var(--elements) 75%, var(--negative-general)); } #patreonLogoutButton:hover i, #patreonLogoutButton:hover i::before { background-color: transparent; } .patreon-status.negative { color: var(--negative-general) !important; } .patreon-status.positive { color: var(--positive-general) !important; } .patreon-status { color: var(--modal-text) !important; } .no-image-by-availability { background-color: color-mix(in srgb, var(--new-primary-dark) 60%, var(--generals)); color: var(--text-general); display: flex; flex-direction: column; align-items: center; gap: 20px; font-size: 18px; padding: 10px; justify-content: center; position: absolute; top: 0; height: 235px; opacity: 0.97; z-index: 5; } .no-image-by-availability i { font-size: 130px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); opacity: 0.2; z-index: 1; } .no-image-by-availability span { text-align: center; z-index: 2; } .saved-status { color: var(--positive-general); display: flex; flex-direction: row; align-items: center; gap: 2px; opacity: 0; transform: translateY(5px); transition: opacity 0.15s, transform 0.15s; } .saved-status.api-loaded { opacity: 1; transform: translateY(0px); } .saved-status i, .remove-key i { display: flex; font-size: 28px; max-height: 28px; } .remove-key { color: var(--modal-text); display: flex; flex-direction: row; align-items: center; gap: 2px; cursor: pointer; opacity: 0; transform: translateY(5px); transition: opacity 0.15s, transform 0.15s; pointer-events: none; margin-left: -7px; } #savedKeySeparator { opacity: 0; transform: translateY(5px); transition: opacity 0.15s, transform 0.15s; pointer-events: none; } .limit-bar-and-text{ display: flex; flex-direction: row; align-items: center; gap: 10px; width: max-content; padding: 4px 8px; border-radius: 4px; } .limit-bar{ height: 8px; width: 100px; background-color: var(--elements); border-radius: 4px; } .limit-bar-fill{ height: 8px; background-color: var(--new-primary); border-radius: 4px; transition: width 0.15s; } .limit-text{ font-size: 14px; color: inherit; } .rate-ok .limit-bar-fill { background-color: var(--rate-ok-color); } .rate-ok.limit-bar-and-text { color: var(--rate-ok-color); background-color: var(--rate-ok-bg); } .rate-warning .limit-bar-fill { background-color: var(--rate-warning-color); } .rate-warning.limit-bar-and-text { color: var(--rate-warning-color); background-color: var(--rate-warning-bg); } .rate-danger .limit-bar-fill { background-color: var(--rate-danger-color); } .rate-danger.limit-bar-and-text { color: var(--rate-danger-color); background-color: var(--rate-danger-bg); } .rate-blocked .limit-bar-fill { background-color: var(--rate-blocked-color); } .rate-blocked.limit-bar-and-text { color: var(--rate-blocked-color); background-color: var(--rate-blocked-bg); } .remove-key i, .remove-key span { transition: color 0.15s, transform 0.15s; } .remove-key:hover i, .remove-key:hover span { color: var(--negative-general); } .remove-key:active span { transform: scale(0.95); } .themes-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(40px, 120px)); gap: 5px; } .one-theme { display: flex; flex-direction: column; align-items: flex-start; padding: 5px 8px 8px 8px; border-radius: 5px; transition: background-color 0.15s; cursor: pointer; background-color: var(--elements); border-radius: 8px; } .one-theme:hover { background-color: var(--elements-hover); } .theme-title { display: flex; flex-direction: row; justify-content: space-between; align-items: center; width: 100%; } .theme-title .bi-check-lg { color: var(--positive-general); transition: all 0.15s; } .one-theme.active .theme-title .bi-check-lg { rotate: 0deg; opacity: 1; } .one-theme:not(.active) .theme-title .bi-check-lg { rotate: 90deg; opacity: 0; } .theme-colors { display: flex; flex-direction: row; width: 100%; } .theme-colors div { height: 30px; flex: 1; } #defaultTheme .background-color { background-color: #202020; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #defaultTheme .primary-color { background-color: #c89efc; } #defaultTheme .secondary-color { background-color: #9AD2CB; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #lightTheme .background-color { background-color: #e7e7e7; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #lightTheme .primary-color { background-color: #7a5fd8; } #lightTheme .secondary-color { background-color: #66aabb; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #ogTheme .background-color { background-color: #1f1b3b; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #ogTheme .primary-color { background-color: #797599; } #ogTheme .secondary-color { background-color: #dedde6; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #vaporwaveTheme .background-color { background-color: #230b55; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #vaporwaveTheme .primary-color { background-color: #ff5ec3; } #vaporwaveTheme .secondary-color { background-color: #00b3ff; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #nightlyTheme .background-color { background-color: #202020; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #nightlyTheme .primary-color { background-color: #9AD2CB; } #nightlyTheme .secondary-color { background-color: #c89efc; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #ferrariTheme .background-color { background-color: #211417; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #ferrariTheme .primary-color { background-color: #ff3d3d; } #ferrariTheme .secondary-color { background-color: #ffb74d; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #redbullTheme .background-color { background-color: #132142; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #redbullTheme .primary-color { background-color: #4b7bff; } #redbullTheme .secondary-color { background-color: #ffd24a; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #mercedesTheme .background-color { background-color: #0e2328; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #mercedesTheme .primary-color { background-color: #00f5d3; } #mercedesTheme .secondary-color { background-color: #c8f9f2; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #astonMartinTheme .background-color { background-color: #07120f; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #astonMartinTheme .primary-color { background-color: #07977b; } #astonMartinTheme .secondary-color { background-color: #c3dc00; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #audiTheme .background-color { background-color: #1a0f12; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #audiTheme .primary-color { background-color: #c00a26; } #audiTheme .secondary-color { background-color: #f1f1f1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #vcarbTheme .background-color { background-color: #101827; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #vcarbTheme .primary-color { background-color: #6c8ff3; } #vcarbTheme .secondary-color { background-color: #f1f1f1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #williamsTheme .background-color { background-color: #0c1526; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #williamsTheme .primary-color { background-color: #1868db; } #williamsTheme .secondary-color { background-color: #f1f1f1; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #haasTheme .background-color { background-color: #17171d; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #haasTheme .primary-color { background-color: #f62039; } #haasTheme .secondary-color { background-color: #c1c1c7; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #alpineTheme .background-color { background-color: #221523; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #alpineTheme .primary-color { background-color: #f168ba; } #alpineTheme .secondary-color { background-color: #47c7fc; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } #mclarenTheme .background-color { background-color: #22160f; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } #mclarenTheme .primary-color { background-color: #ff8000; } #mclarenTheme .secondary-color { background-color: #47c7fc; border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .big-subtitle p { padding-top: 5px; color: var(--new-primary) } .big-subtitle { -webkit-box-sizing: content-box; box-sizing: content-box; height: 50px; display: -webkit-box; display: -ms-flexbox; display: flex; border-radius: 8px; margin-top: -5px; font-size: 24px; } .words { overflow: hidden; position: relative; } .words::after { content: ""; position: absolute; inset: 0; background: linear-gradient(var(--superficials) 10%, transparent 30%, transparent 70%, var(--superficials) 90%); z-index: 20; } .word { display: block; height: 100%; padding-left: 6px; padding-top: 5px; color: var(--new-primary); animation: move-gradient-title 5s linear infinite, spin_4991 15s infinite; will-change: transform; } @keyframes spin_4991 { 0% { transform: translateY(0%); } 25% { transform: translateY(0%); } 27% { transform: translateY(-102%); } 30% { transform: translateY(-100%); } 32% { transform: translateY(-202%); } 36% { transform: translateY(-200%); } 38% { transform: translateY(-302%); } 42% { transform: translateY(-300%); } 44% { transform: translateY(-402%); } 48% { transform: translateY(-400%); } 50% { transform: translateY(-502%); } 54% { transform: translateY(-500%); } 56% { transform: translateY(-602%); } 60% { transform: translateY(-600%); } 62% { transform: translateY(-702%); } 66% { transform: translateY(-700%); } 68% { transform: translateY(-802%); } 72% { transform: translateY(-800%); } 74% { transform: translateY(-902%); } 78% { transform: translateY(-900%); } 80% { transform: translateY(-1002%); } 84% { transform: translateY(-1000%); } 100% { transform: translateY(-1000%); } } .everywhere { opacity: 1; animation: everywhere 15s infinite; transform: translateX(-10px); } @keyframes everywhere { 0% { opacity: 1; } 25% { opacity: 1; } 27% { opacity: 0; } 88% { opacity: 0; } 90% { opacity: 1; } 100% { opacity: 1; } } .patreon-members { position: absolute; top: 0; left: 0; z-index: 1; width: 100%; height: 50px; overflow: hidden; display: flex; align-items: center; } .marquee { white-space: nowrap; overflow: hidden; height: 100%; display: flex; align-items: center; } .marquee__inner { display: flex; animation: scroll 165s linear infinite; gap: 1em; } .marquee__group { display: flex; gap: 1em; } .marquee__group span { font-size: 20px; font-weight: bold; } /* Estilos por tier */ .backer { color: color-mix(in srgb, var(--background) 40%, var(--dark-text)) } .supporter { color: color-mix(in srgb, var(--background) 40%, var(--disabled-text)) } .founder { color: color-mix(in srgb, var(--background) 40%, var(--table-first)) } body.light-theme .founder { color: color-mix(in srgb, var(--dark-text) 40%, var(--table-first)) } /* Animación de desplazamiento */ @keyframes scroll { 0% { transform: translateX(-50%); } /* Empieza con la segunda mitad visible */ 100% { transform: translateX(9px); } /* Vuelve a la posición inicial */ } .changes-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 25px; position: relative; } .one-change { background-color: color-mix(in srgb, var(--elements) 60%, transparent); padding: 15px 20px; border-radius: 10px; display: flex; flex-direction: column; gap: 10px; align-items: center; justify-content: space-between; backdrop-filter: blur(2px); box-shadow: var(--shadow-s); opacity: 0; } body.light-theme .one-change { background-color: #f5f5f5; } body.light-theme .one-change .one-change-description { color: #282828; } .one-change-title { text-transform: uppercase; font-size: 20px; color: var(--text-general); text-align: center; } .one-change-description { font-size: 16px; color: var(--modal-text); } .one-change-description.add-results-team { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .one-change-description.add-results-team .add-results-team__left { display: inline-flex; align-items: center; gap: 10px; min-width: 0; } .one-change-description.add-results-team .add-results-team__logo { width: 26px; height: 18px; flex: 0 0 auto; object-fit: contain; } .one-change-description.add-results-team .add-results-team__right { display: inline-flex; align-items: baseline; gap: 6px; white-space: nowrap; flex: 0 0 auto; } .one-change-description.add-results-team .add-results-points { font-size: 18px; line-height: 1; transition: color 0.15s; } .add-results-points.activated{ color: var(--dark-text); } .calendar-changes-2026{ transition: transform 0.15s ease; } .one-change:has(.completed) .calendar-changes-2026{ transform: translateX(-25px) } .one-change-description.regs-team { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .one-change-description.regs-team .regs-team__name { text-transform: uppercase; white-space: nowrap; } .one-change-description.regs-team .regs-team__logo { position: relative; width: 26px; height: 18px; flex: 0 0 auto; } .one-change-description.regs-team .regs-team__logo::before, .one-change-description.regs-team .regs-team__logo::after { content: ''; position: absolute; inset: 0; background-repeat: no-repeat; background-position: center; background-size: contain; opacity: 0; transform: translateY(-10px); transition: opacity 0.2s ease, transform 0.2s ease; } .one-change-description.regs-team .regs-team__logo::before { opacity: 1; transform: translateY(0); } .one-change:has(.change-performance-2026.completed) .one-change-description.regs-team .regs-team__logo::before { opacity: 0; transform: translateY(10px); } .one-change:has(.change-performance-2026.completed) .one-change-description.regs-team .regs-team__logo::after { opacity: 1; transform: translateY(0); } .one-change-description.regs-team--aston .regs-team__logo::before { background-image: url('/assets/images/logos/mercedes.png'); } .one-change-description.regs-team--aston .regs-team__logo::after { background-image: url('/assets/images/logos/honda.png'); } .one-change-description.regs-team--stake .regs-team__logo::before { background-image: url('/assets/images/logos/ferrari.png'); } .one-change-description.regs-team--stake .regs-team__logo::after { background-image: url('/assets/images/logos/audi.png'); } .one-change-description.regs-team--stake .regs-team__name { position: relative; display: inline-block; min-height: 1em; color: transparent; } .one-change-description.regs-team--stake .regs-team__name::before, .one-change-description.regs-team--stake .regs-team__name::after { position: absolute; top: 0; left: 0; opacity: 0; transform: translateY(-10px); transition: opacity 0.2s ease, transform 0.2s ease; color: var(--modal-text); } body.light-theme .one-change-description.regs-team--stake .regs-team__name::before, body.light-theme .one-change-description.regs-team--stake .regs-team__name::after { color: #282828; } .one-change-description.regs-team--stake .regs-team__name::before { content: 'Stake Sauber'; opacity: 1; transform: translateY(0); } .one-change-description.regs-team--stake .regs-team__name::after { content: 'Audi'; font-family: 'Formula1Bold'; color: var(--dark-text); } .one-change:has(.change-performance-2026.completed) .one-change-description.regs-team--stake .regs-team__name::before { opacity: 0; transform: translateY(10px); } .one-change:has(.change-performance-2026.completed) .one-change-description.regs-team--stake .regs-team__name::after { opacity: 1; transform: translateY(0); } .one-change-description.lineup-team { display: flex; align-items: center; justify-content: space-between; gap: 12px; } .one-change-description.lineup-team .lineup-team__name { text-transform: uppercase; white-space: nowrap; } .one-change-description.lineup-team .lineup-team__logo { position: relative; width: 28px; height: 18px; flex: 0 0 auto; } .one-change-description.lineup-team .lineup-team__logo::before, .one-change-description.lineup-team .lineup-team__logo::after { content: ''; position: absolute; inset: 0; background-repeat: no-repeat; background-position: center; background-size: contain; opacity: 0; transform: translateY(-10px); transition: opacity 0.22s ease, transform 0.22s ease; } .one-change-description.lineup-team .lineup-team__logo::before { opacity: 1; transform: translateY(0); } .one-change:has(.change-line-ups-2026.completed) .one-change-description.lineup-team .lineup-team__logo::before { opacity: 0; transform: translateY(10px); } .one-change:has(.change-line-ups-2026.completed) .one-change-description.lineup-team .lineup-team__logo::after { opacity: 1; transform: translateY(0); } /* 2026 Line-ups logos (right side) */ .one-change-description.lineup-team--cadillac .lineup-team__logo::before { /* nothing shown until applied */ opacity: 0; transform: translateY(-10px); } .one-change-description.lineup-team--cadillac .lineup-team__logo::after { background-image: url('/assets/images/logos/cadillac.png'); height: 26px; } .one-change:has(.change-line-ups-2026.completed) .one-change-description.lineup-team--cadillac .lineup-team__logo::after{ transform: translateY(-3px); } .one-change-description.lineup-team--hadjar .lineup-team__logo::before { background-image: url('/assets/images/logos/visarb.png'); } .one-change-description.lineup-team--hadjar .lineup-team__logo::after { background-image: url('/assets/images/logos/redbull.png'); } .one-change-description.lineup-team--lindblad .lineup-team__logo::before { background-image: url('/assets/images/logos/campos.png'); } .one-change-description.lineup-team--lindblad .lineup-team__logo::after { background-image: url('/assets/images/logos/visarb.png'); } .one-change-description.engine-renamed.engine-renamed-honda, .one-change-description.engine-appear { color: var(--text-general); } .one-change-description.performance-year-switch { position: relative; display: inline-flex; align-items: center; justify-content: center; gap: 6px; } .one-change-description.performance-year-switch .performance-year-switch__year { position: relative; display: inline-block; min-width: 44px; min-height: 1em; color: transparent; } .one-change-description.performance-year-switch .performance-year-switch__year::before, .one-change-description.performance-year-switch .performance-year-switch__year::after { position: absolute; top: -5px; left: 0; opacity: 0; transform: translateY(-10px); transition: opacity 0.2s ease, transform 0.2s ease; color: var(--modal-text); } body.light-theme .one-change-description.performance-year-switch .performance-year-switch__year::before { color: #282828; } .one-change-description.performance-year-switch .performance-year-switch__year::before { content: '2024'; opacity: 1; transform: translateY(0); } .one-change-description.performance-year-switch .performance-year-switch__year::after { content: '2026'; color: var(--dark-text); } .one-change:has(.change-performance-2026.completed) .one-change-description.performance-year-switch .performance-year-switch__year::before { opacity: 0; transform: translateY(10px); } .one-change:has(.change-performance-2026.completed) .one-change-description.performance-year-switch .performance-year-switch__year::after { opacity: 1; transform: translateY(0); } .one-change-description.engine-appear { opacity: 0; transform: translateY(10px); transition: opacity 0.15s ease, transform 0.15s ease, max-height 0.15s ease; } .one-change-description.engine-appear.engine-appear-visible { opacity: 1; transform: translateY(0); } .engine-renamed{ min-height: 24px; } button.one-change-button { background-color: var(--text-general); color: var(--negative-text); border: none; padding: 7px 13px; border-radius: 7px; cursor: pointer; transition: color 0.15s, background-color 0.15s, box-shadow 0.15s; margin-top: 5px; width: 100%; } #mods2026View button.one-change-button { background-color: transparent; background-repeat: no-repeat; background-size: 100% 100%; background-image: linear-gradient( 90deg, var(--text-general) 0%, var(--text-general) 50%, var(--text-general) 100% ); transition: color 0.15s, background-image 0.35s ease, box-shadow 0.15s; } #mods2026View button.one-change-button:hover { background-color: transparent; background-image: linear-gradient( 90deg, var(--new-primary) 0%, var(--new-primary) 50%, var(--new-primary) 100% ); } button.one-change-button.disabled { filter: brightness(0.6); cursor: default; pointer-events: none; } button.one-change-button:hover { background-color: var(--new-primary); box-shadow: 0 0 5px var(--new-primary); } button.one-change-button:active { transform: scale(0.98); /* Ajusta esto a tu gusto */ box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.2); } button.one-change-button.completed { background-color: transparent; color: var(--positive-general); cursor: default; pointer-events: none; } #mods2026View button.one-change-button.completed { background-image: linear-gradient( 90deg, transparent 0%, var(--positive-transparent) 50%, transparent 100% ); } button.one-change-button.completed:hover { background-color: transparent; box-shadow: none; } button.one-change-button .bi-check-lg { transition: all 0.15s; } button.one-change-button:not(.completed) .bi-check-lg { rotate: 90deg; opacity: 0; } button.one-change-button.completed .bi-check-lg { rotate: 0deg; opacity: 1; } button.one-change-button span { padding-right: 16px; transition: all 0.15s; } button.one-change-button.completed span { padding-right: 0; } @keyframes change-appear { 0% { opacity: 0; transform: translateY(20px); } 100% { opacity: 1; transform: translateY(0); } } body:has(#modpill.active) .one-change { animation: change-appear 0.6s ease-out forwards; animation-delay: calc(0.8s + var(--order) * 0.1s); } .ham-transfer, .sai-transfer, .ant-transfer { color: transparent; position: relative; } .bor-ovr, .ant-ovr, .pia-ovr { transform: skewX(-9deg); display: inline-block; min-width: 122px; } .had-ovr, .bea-ovr, #mods2026View .ant-ovr { transform: skewX(0deg) !important; display: inline-block; min-width: 122px; transition: color 0.15s; } .ovr-change { color: var(--dark-text); } .ham-transfer::before, .sai-transfer::before, .ant-transfer::before { font-family: 'Formula1Bold'; transition: all 0.15s; transform: skewX(-9deg); display: inline-block; } .ham-transfer::before { content: 'HAMILTON'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; color: var(--mercedes-primary); } .sai-transfer::before { content: 'SAINZ'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; color: var(--ferrari-primary); } .ant-transfer::before { content: 'ANTONELLI'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; color: var(--modal-text); } .one-change:has(.completed) .ham-transfer, .one-change:has(.completed) .sai-transfer, .one-change:has(.completed) .ant-transfer { color: var(--modal-text); } .one-change:has(.completed) .ham-transfer::before { transform: translateX(59px) skewX(-9deg); color: var(--ferrari-primary); } .one-change:has(.completed) .sai-transfer::before { transform: translateX(62px) skewX(-9deg); color: var(--williams-primary); } .ant-team { transform: translateX(22px); padding-left: 50px; } .ham-team { padding-left: 51px; transition: all 0.15s; } .sai-team { transition: all 0.15s; } .one-change:has(.completed) .ham-team, .one-change:has(.completed) .sai-team, .one-change:has(.completed) .ant-team { color: transparent; padding: 0; } .one-change:has(.completed) .ant-transfer::before { transform: translateX(65px) skewX(-9deg); color: var(--mercedes-primary); } .date-holder { display: inline-flex; gap: 5px; } .date-part { position: relative; display: inline-block; text-align: center; overflow: hidden; } @keyframes slide-down { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } @keyframes slide-out-down { from { transform: translateY(0); opacity: 1; } to { transform: translateY(20px); opacity: 0; } } #dateDay::before { content: '29th'; position: absolute; font-family: 'Formula1Bold'; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } #dateDay2026, #dateMonth2026, #dateYear2026 { --date-switch-delay: 0s; } #dateMonth2026 { --date-switch-delay: 0.05s; } #dateYear2026 { --date-switch-delay: 0.1s; } #dateDay2026, #dateMonth2026, #dateYear2026 { transition: transform 0.2s ease, color 0.2s ease; transition-delay: var(--date-switch-delay); transform: translateY(0); overflow: visible; } #dateDay2026::after, #dateMonth2026::after, #dateYear2026::after { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; transform: translateY(-20px); transition: opacity 0.2s ease, transform 0.2s ease; transition-delay: var(--date-switch-delay); color: var(--dark-text); font-family: 'Formula1Bold'; pointer-events: none; z-index: 1; } #dateDay2026::after { content: '29th'; } #dateMonth2026::after { content: 'December'; } #dateYear2026::after { content: '2025'; } .one-change:has(.completed) #dateDay2026, .one-change:has(.completed) #dateMonth2026, .one-change:has(.completed) #dateYear2026 { transform: translateY(10px); color: transparent; } .one-change:has(.completed) #dateDay2026::after, .one-change:has(.completed) #dateMonth2026::after, .one-change:has(.completed) #dateYear2026::after { opacity: 1; transform: translateY(-10px); } #dateMonth, #dateMonth2026 { min-width: 100px; } #dateDay, #dateDay2026 { min-width: 50px; } #dateMonth::before { content: 'December'; font-family: 'Formula1Bold'; position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.2s; color: var(--dark-text); } #dateYear::before { content: '2024'; position: absolute; font-family: 'Formula1Bold'; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.3s; color: var(--dark-text); } .one-change:has(.completed) #dateDay::before { animation: slide-down 0.3s forwards 0s; } .one-change:has(.completed) #dateMonth::before { animation: slide-down 0.3s forwards 0.05s; } .one-change:has(.completed) #dateYear::before { animation: slide-down 0.3s forwards 0.1s; } .one-change:has(.completed) #dateDay, .one-change:has(.completed) #dateMonth, .one-change:has(.completed) #dateYear { transition: color 0.15s; color: transparent; } .bor-change, .ant-change, .pia-change, .bea-change, .had-change { position: relative; overflow: hidden; color: transparent; } .bor-change::before { content: '78'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; transform: translateY(-20px); color: var(--dark-text); } .bor-change::after { content: '69'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; transform: translateY(0); color: var(--dark-text); } .one-change:has(.completed) .bor-change::before { animation: slide-down 0.3s forwards 0s; } .one-change:has(.completed) .bor-change::after { animation: slide-out-down 0.3s forwards 0s; } .ant-change::before { content: '77'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; transform: translateY(-20px); color: var(--dark-text); } #mods2026View .ant-change::before{ content: "86"; font-family: "NumbersFont"; } #mods2026View .ant-change::after{ font-family: "NumbersFont"; } .ant-change::after { content: '71'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; transform: translateY(0); color: var(--dark-text); } .one-change:has(.completed) .ant-change::before { animation: slide-down 0.3s forwards 0.05s; } .one-change:has(.completed) .ant-change::after { animation: slide-out-down 0.3s forwards 0.05s; } .pia-change::before { content: '86'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; transform: translateY(-20px); color: var(--dark-text); } .pia-change::after { content: '84'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; transform: translateY(0); color: var(--dark-text); } .one-change:has(.completed) .pia-change::before { animation: slide-down 0.3s forwards 0.1s; } .one-change:has(.completed) .pia-change::after { animation: slide-out-down 0.3s forwards 0.1s; } .had-change::before { content: '83'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; transform: translateY(-20px); color: var(--dark-text); font-family: "NumbersFont"; } .had-change::after { content: '75'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; transform: translateY(0); color: var(--dark-text); font-family: "NumbersFont"; } .one-change:has(.completed) .had-change::before { animation: slide-down 0.3s forwards 0s; } .one-change:has(.completed) .had-change::after { animation: slide-out-down 0.3s forwards 0s; } .bea-change::before { content: '84'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: 2; transform: translateY(-20px); color: var(--dark-text); font-family: "NumbersFont"; } .bea-change::after { content: '77'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 1; z-index: 1; transform: translateY(0); color: var(--dark-text); font-family: "NumbersFont"; } .one-change:has(.completed) .bea-change::before { animation: slide-down 0.3s forwards 0.1s; } .one-change:has(.completed) .bea-change::after { animation: slide-out-down 0.3s forwards 0.1s; } .fast-lap-change::before { content: '0'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-10px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .fast-lap-change::before { animation: slide-down 0.3s forwards 0.05s; } .fast-lap-change { position: relative; margin-right: 2px; } .one-change:has(.completed) .fast-lap-change { color: transparent; } .fast-lap-general { color: #c90fd7; transition: all 0.15s; } .one-change:has(.completed) .fast-lap-general { color: var(--dark-text); } .cost-cap-change::before { content: '215M €'; position: absolute; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-10px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .cost-cap-change::before { animation: slide-down 0.3s forwards 0.05s; } .cost-cap-change { position: relative; } .one-change:has(.completed) .cost-cap-change { color: transparent; } .cost-cap-general { transition: all 0.15s; } .one-change:has(.completed) .cost-cap-general { color: var(--dark-text); } .mclaren-cfd, .redbull-cfd, .ferrari-cfd { position: relative; } .mclaren-cfd::before { content: '4.2h'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .mclaren-cfd::before { animation: slide-down 0.3s forwards 0.05s; } .one-change:has(.completed) .mclaren-cfd { color: transparent; } .redbull-cfd::before { content: '4.8h'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .redbull-cfd::before { animation: slide-down 0.3s forwards 0s; } .one-change:has(.completed) .redbull-cfd { color: transparent; } .ferrari-cfd::before { content: '4.5h'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .ferrari-cfd::before { animation: slide-down 0.3s forwards 0.1s; } .one-change:has(.completed) .ferrari-cfd { color: transparent; } .cfd-name { min-width: 125px; display: inline-block; color: var(--dark-text); } .one-change-performance-name { min-width: 100px; display: inline-block; color: var(--dark-text); } .missing-staff, .missing-f2, .missing-academy { position: relative; width: 225px; transition: width 0.15s; } .missing-staff::before, .missing-f2::before, .missing-academy::before { content: 'Added!'; position: absolute; font-family: "Formula1Bold"; bottom: 0px; left: 0; height: 100%; opacity: 0; z-index: -1; transform: translateX(0); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .missing-staff::before { transform: translateX(120px); opacity: 1; color: var(--positive-general); } .one-change:has(.completed) .missing-f2::before { transform: translateX(170px); opacity: 1; color: var(--positive-general); } .one-change:has(.completed) .missing-academy::before { transform: translateX(227px); opacity: 1; color: var(--positive-general); } .one-change:has(.completed) .missing-f2, .one-change:has(.completed) .missing-staff, .one-change:has(.completed) .missing-academy { width: 280px; } .season-change::before { content: 'Australia'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .sprint-change::before { content: 'Belgium'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .sprint-change-2026.change-1::before{ content: 'Canada,'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .sprint-change-2026.change-2::before{ content: 'Silverstone,'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 0; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .sprint-change-2026.change-3::before{ content: 'Zandvoort,'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 55px; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .sprint-change-2026.change-4::before{ content: 'Singapore'; position: absolute; font-family: "Formula1Bold"; bottom: 2px; left: 100px; width: 100%; height: 100%; opacity: 0; z-index: -1; transform: translateY(-20px); animation-delay: 0.1s; color: var(--dark-text); } .one-change:has(.completed) .season-change::before { animation: slide-down 0.3s forwards 0s; } .one-change:has(.completed) .season-change { color: transparent; } .season-change { position: relative; } .one-change:has(.completed) .sprint-change::before, .one-change:has(.completed) .sprint-change-2026::before{ animation: slide-down 0.3s forwards 0.05s; } .one-change:has(.completed) .sprint-change, .one-change:has(.completed) .sprint-change-2026 { color: transparent; } .sprint-change, .sprint-change-2026 { position: relative; } .mod-blocking { display: flex; flex-direction: column; align-items: center; gap: 10px; } .mod-blocking:not(.mods-2026-blocking){ margin-top: 50px; } .mod-blocking-title { font-size: 24px; color: #dedde6; font-family: 'Formula1Bold'; } .mod-blocking-subtitle { font-size: 18px; color: var(--modal-text); } .one-change-description.performance-description { display: flex; flex-direction: row; gap: 5px; align-items: flex-end; } .one-change-bar-container { background-color: var(--background); width: 100px; height: 4px; margin-bottom: 7px; } .one-change-bar-fill.performance-bar-progress { height: 4px; position: relative; } .one-change-bar-fill.performance-bar-progress.rb { width: 58.5%; transition: width 0.15s; } .one-change-bar-fill.performance-bar-progress.mc { width: 56.5%; transition: width 0.15s; } .one-change-bar-fill.performance-bar-progress.wi { width: 46.1%; transition: width 0.15s; } .mclaren-performance, .williams-performance, .redbull-performance { transition: all 0.15s; } .one-change:has(.completed) .one-change-bar-fill.performance-bar-progress.rb { width: 60.42%; } .one-change:has(.completed) .mclaren-performance, .one-change:has(.completed) .williams-performance, .one-change:has(.completed) .redbull-performance { color: var(--dark-text) } .one-change:has(.completed) .one-change-bar-fill.performance-bar-progress.mc { width: 65.77%; } .one-change:has(.completed) .one-change-bar-fill.performance-bar-progress.wi { width: 56.85%; } .mclaren-performance-description, .redbull-performance-description, .williams-performance-description { transition: all 0.15s; } .one-change:has(.completed) .mclaren-performance-description { transform: translateY(-24px); } .one-change:has(.completed) .redbull-performance-description { transform: translateY(24px); } .news-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(450px, 1fr)); gap: 25px; padding: 0 14px; overflow-y: auto; max-height: calc(100vh - 160px); margin-right: 8px; } .news-item-clone .news-image-container { max-width: 600px; } .news-item-clone.expanded .news-image-container, .news-item-clone.expanded .news-image { border-top-right-radius: 0px; } .modal-header:has(img.news-image-background.d-none) { background: var(--primary-gradient) !important; } .news-item { background-color: var(--elements); border-radius: 10px; display: flex; flex-direction: column; z-index: 0; transition: none; box-shadow: var(--shadow-s); } .news-item.with-transition { transition: transform 0.15s ease-out, opacity 0.15s ease-out; } .news-item.opened { transform: translateY(-10px); opacity: 0 !important; } .news-item:not(.news-item-clone) { opacity: 0; } .news-item.active { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(1.05); z-index: 999; } .news-item.fade-in:not(.news-item-clone) { animation: fadeInHigher 0.2s forwards; animation-delay: calc(var(--order) * 0.05s); } .news-body { padding: 10px; display: flex; flex-direction: column; justify-content: space-between; flex: 1; gap: 10px; min-height: 112px; } .title-and-article { display: flex; flex-direction: column; align-items: flex-start; } .title-and-article:has(.disabled-title) { flex-direction: row; align-items: center; gap: 10px; } .title-and-article:has(.disabled-title) i { color: var(--disabled-text); font-size: 20px; } .news-article h1 { font-size: 34px; font-family: 'Formula1Bold'; color: var(--new-primary); margin-bottom: 0; } .news-article h2 { font-size: 28px; font-family: 'Formula1Bold'; color: var(--text-general); margin-bottom: 0; } .news-article h3 { font-size: 22px; font-family: 'Formula1Bold'; margin-bottom: 0; color: var(--text-secondary); } .news-article ul{ margin-bottom: 0; display: flex; flex-direction: column; gap: 4px; padding-left: 28px; } .news-article p { margin: 0; color: var(--text-secondary); } .news-article em { color: var(--text-selected); } .news-article strong { color: var(--text-general); font-family: 'Formula1Bold'; } /* not first li (that doesn't have a p as a child) in a ul to have negative margin-top */ .news-article ul li:not(:first-child):has(p:first-child) { margin-top: -15px; } .news-article-date { color: var(--news-date-text); display: flex; gap: 10px; align-items: center; margin-bottom: 7px; opacity: 1; transition: opacity 0.15s; z-index: 1; background: color-mix(in srgb, var(--text-general) 30%, transparent); padding: 3px 8px; border-radius: 8px; backdrop-filter: blur(10px); margin-top: 10px; } body.default-theme .news-article-date { background: color-mix(in srgb, white 30%, transparent); } .news-article-date.show { opacity: 1; } .news-article-date i { font-size: 20px; } .news-article { color: color-mix(in srgb, var(--text-general) 80%, gray); overflow-y: auto; padding-right: 10px; transition: opacity 0.15s ease; height: 60vh; display: flex; flex-direction: column; gap: 12px; } .news-article:has(.news-edit-textarea), .news-article:has(.news-edit-title){ padding-right: 0; } .modal-content { background-color: var(--generals) !important; /* evita el “hairline” durante la animación */ } .modal-header { border-bottom: 0 !important; } .news-image-background { width: 100%; position: absolute; top: 0; left: 0; opacity: 0.5; object-fit: cover; height: 100%; z-index: 0; border-top-left-radius: 10px; border-top-right-radius: 10px; } #newsModal .modal-footer { border-top: 0 !important; } .news-edit-textarea { background-color: transparent; border: none; resize: none; width: 100%; height: 100%; padding-right: 10px; min-height: 320px; color: var(--text-general); font: inherit; outline: none; } .news-edit-title { resize: none; background: transparent; color: var(--text-general); font: inherit; font-weight: 700; border: none; border-bottom: 2px solid var(--text-secondary); outline: none; box-sizing: border-box; padding: 0; margin: 0; overflow: hidden; white-space: nowrap; overflow-x: hidden; overflow-y: hidden; field-sizing: content; height: 42px; } #newsModalTitle { z-index: 1; } #newsModalTitle:has(textarea){ height: 42px; } .news-title { font-size: 20px; color: var(--text-general); line-height: 1.2; /* transition: font-size 0.15s; */ } .news-title.disabled-title { color: var(--disabled-text); } .news-item-clone .news-title { font-size: 28px; } #newsModal .modal-body { display: flex; flex-direction: column; gap: 10px; padding-top: 15px; } .modal-body .news-image-container { height: auto; width: 600px; align-self: center; } .news-image-container { width: 100%; height: 235px; overflow: hidden; border-top-right-radius: 10px; border-top-left-radius: 10px; position: relative; } /* .news-image-container img{ padding: 1px 0px 0px 0px; } */ .breaking-news-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--primary-gradient); display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 20px; z-index: 1; color: white; font-weight: bold; text-transform: uppercase; } .breaking-news-breaking { font-size: 50px; letter-spacing: 0.1em; text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); margin-bottom: -0.2em; } .breaking-news-news { font-size: 50px; letter-spacing: 0.2em; text-shadow: 0 4px 8px rgba(0, 0, 0, 0.5); } .breaking-news-bar { position: absolute; bottom: 0; left: 0; width: 100%; height: 8px; background: #ffffff; } .hide-historic-drivers.active .bi-eye { display: none; } .hide-historic-drivers.active .bi-eye-slash { display: inline; } .hide-historic-drivers .bi-eye { display: inline; } .hide-historic-drivers .bi-eye-slash { display: none; } .reload-article { opacity: 1; } .reload-news, .hide-historic-drivers { display: flex; flex-direction: row; align-items: center; gap: 5px; color: var(--dark-text); cursor: pointer; transition: color 0.15s, transfrorm 0.15s; font-size: 16px; } .reload-news::selection, .hide-historic-drivers::selection { background: transparent; } .reload-news:hover, .hide-historic-drivers:hover { color: var(--new-primary); } .reload-news:active span, .hide-historic-drivers:active span { transform: scale(0.96); } .race-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to right, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.05)); display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-end; padding: 10px 15px 4px 15px; z-index: 2; } .reaction-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to right, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.05)); display: flex; align-items: center; justify-content: center; padding: 10px 15px; z-index: 2; } .news-centered-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(to right, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.05)); display: flex; align-items: center; justify-content: center; padding: 10px 15px; z-index: 2; } .news-centered-overlay .news-centered-block { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 6px 12px; border-bottom: 3px solid transparent; color: var(--news-overlays-text); font-family: 'Formula1Bold'; } .news-centered-overlay .news-centered-title { font-size: 26px; letter-spacing: 1px; text-transform: uppercase; text-align: center; } .news-centered-overlay .news-centered-subtitle { font-size: 16px; letter-spacing: 0.5px; opacity: 0.9; text-align: center; text-transform: uppercase; } .news-centered-overlay .news-centered-subtitle.silly-season-names { font-size: 18px; letter-spacing: 1px; opacity: 1; } .reaction-overlay .reaction-title { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; padding: 6px 12px; border-bottom: 3px solid transparent; color: var(--news-overlays-text); font-family: 'Formula1Bold'; text-transform: uppercase; letter-spacing: 1px; } .reaction-overlay .reaction-title-text { font-size: 26px; } .reaction-overlay .reaction-race-name { font-size: 16px; letter-spacing: 0.5px; opacity: 0.9; } .race-overlay .position { font-size: 20px; color: var(--news-overlays-text); font-family: 'Formula1Bold'; text-align: center; min-width: 45%; text-align: left; text-transform: uppercase; } .race-overlay .position.firstpos { font-size: 24px; min-width: 50%; } .news-image { width: 100%; height: 100%; border-top-right-radius: 10px; border-top-left-radius: 10px; object-fit: cover; opacity: 1; transition: opacity 0.15s ease; } .news-image.loaded { opacity: 1; } .position-team { padding-left: 4px; font-size: 16px; line-height: 1; margin-bottom: 10px; margin-top: -3px; color: var(--news-overlays-text); } .position-team.firstpost { margin-bottom: 0px; } @media (max-height: 768px) { .expanded .read-button-container { position: absolute; top: 10px; right: 10px; } } .read-button-container { display: flex; flex-direction: row; justify-content: flex-end; min-height: 30px; } .read-button-container:has(.turning-point-div) { justify-content: space-between; } .read-actions { display: flex; align-items: center; gap: 3px; justify-content: flex-end; } .read-button { border-radius: 5px; background-color: var(--elements); color: var(--dark-text); align-self: flex-end; padding: 5px 10px; cursor: pointer; position: relative; transition: background-color 0.15s, color 0.15s; } .context-read-button { border-radius: 5px; background-color: var(--elements); color: var(--dark-text); align-self: flex-end; padding: 5px 8px; cursor: pointer; position: relative; display: inline-flex; align-items: center; justify-content: center; opacity: 0; pointer-events: none; transform: translateX(6px); transition: opacity 0.15s ease, transform 0.15s ease, background-color 0.15s, color 0.15s; } .context-read-button i { font-size: 16px; } .news-item:hover .context-read-button, .news-item:focus-within .context-read-button { opacity: 1; pointer-events: auto; transform: translateX(0); } .context-read-button:hover { background-color: var(--elements-hover); color: var(--text-general); } .news-context-textarea { width: 100%; min-height: 140px; resize: vertical; border-radius: 8px; border: 1px solid var(--elements-hover); background-color: color-mix(in srgb, var(--elements) 70%, transparent); color: var(--text-general); padding: 8px 10px; outline: none; } .news-context-textarea:focus { border-color: var(--new-primary); box-shadow: 0 0 0 1px color-mix(in srgb, var(--new-primary) 50%, transparent); } .close-modal, .confirm-modal { border-radius: 5px; background-color: transparent; color: var(--dark-text); padding: 5px 10px; cursor: pointer; position: relative; transition: background-color 0.15s, color 0.15s; border: none; } .close-modal:hover { background-color: color-mix(in srgb, var(--elements) 75%, var(--negative-general)); color: var(--negative-general); } .confirm-modal:hover { background-color: color-mix(in srgb, var(--elements) 75%, var(--new-primary)); color: var(--new-primary); } .read-button:hover { background-color: var(--elements-hover); color: var(--text-general); } .read-button:active, .close-modal:active, .confirm-modal:active { transform: scale(0.96); } @property --angle { syntax: ""; initial-value: 0deg; inherits: false; } .read-button:not(.closable):hover::after { opacity: 1; } @keyframes spin { from { --angle: 0deg; } to { --angle: 360deg; } } .session { background-color: color-mix(in srgb, white 20%, transparent); display: flex; align-items: center; justify-content: center; color: var(--news-overlays-text); padding: 5px 18px; text-transform: uppercase; font-size: 16px; margin-bottom: 7px; border-radius: 7px; } .news-error.model-error { background-color: color-mix(in srgb, var(--negative-general) 20%, var(--elements)); padding: 15px 20px; border-radius: 10px; border: 1px solid var(--negative-general); color: var(--negative-general); text-align: center; width: max-content; } .news-error.model-retry { text-align: center; } span.news-error { color: color-mix(in srgb, var(--elements) 60%, var(--text-general)); } p.news-error-api-key { display: inline-block; top: 60%; text-align: center; color: color-mix(in srgb, var(--elements) 60%, var(--text-general)); text-wrap: wrap; white-space: normal; hyphens: none; } .loader-div, .news-error { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); transition: opacity 0.15s; opacity: 1; } .loader-div.show { opacity: 1; } .loader-div span { font-size: 20px; color: var(--text-muted) } .ai-progress-bar { height: 7px; box-shadow: 0 0 0 2px var(--new-primary) inset; background-color: transparent; min-width: 160px; border-radius: 5px; overflow: hidden; outline: none; } .progress-div { width: 0%; height: 100%; border-radius: 5px; background-color: var(--new-primary); transition: width 0.15s; will-change: width; } .loading-dots { width: 15px; display: inline-block; } .word-fade { opacity: 0; transition: opacity 0.15s ease; } @keyframes slide-in-from-right { from { transform: translateX(50px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } /* Keyframes para entrada desde la izquierda */ @keyframes slide-in-from-left { from { transform: translateX(-50px); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .new-number { font-family: "NumbersFont"; min-width: 27px; display: inline-block; } #newsTypeMenu .redesigned-dropdown-item, #newsLanguageMenu .redesigned-dropdown-item{ min-width: 200px; } #newsTypeMenu .redesigned-dropdown-item i, #newsLanguageMenu .redesigned-dropdown-item i { display: inline-block; font-size: 18px; margin-left: 8px; transition: transform 0.15s, opacity 0.15s; transform: rotate(0deg); } #season_viewer .redesigned-dropdown-menu .redesigned-dropdown-item i.bi-check { display: inline-block; font-size: 18px; margin-left: 8px; transition: transform 0.15s, opacity 0.15s; transform: rotate(0deg); } #newsTypeMenu .redesigned-dropdown-item i.unactive, #newsLanguageMenu .redesigned-dropdown-item i.unactive, #season_viewer .redesigned-dropdown-menu .redesigned-dropdown-item i.unactive, #aiModelmenu .dropdown-item i.unactive { transform: rotate(-90deg); opacity: 0; } #customNewsModal .redesigned-dropdown-item i { display: inline-block; font-size: 18px; margin-left: 8px; transition: transform 0.15s, opacity 0.15s; transform: rotate(0deg); } #customNewsModal .redesigned-dropdown-item i.unactive { transform: rotate(-90deg); opacity: 0; } #customNewsModal .custom-news-row { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 16px; align-items: start; } #customNewsModal #customNewsForm > .custom-news-row { margin-bottom: 16px; } #customNewsModal #customNewsForm > .custom-news-row:last-of-type { margin-bottom: 0; } #customNewsModal .custom-news-stack { display: flex; flex-direction: column; gap: 6px; min-width: 0; grid-column: span 12; } #customNewsModal .custom-news-stack[data-span="3"] { grid-column: span 3; } #customNewsModal .custom-news-stack[data-span="4"] { grid-column: span 4; } #customNewsModal .custom-news-stack[data-span="6"] { grid-column: span 6; } #customNewsModal .custom-news-stack[data-span="12"] { grid-column: span 12; } @media (max-width: 768px) { #customNewsModal .custom-news-stack { grid-column: 1 / -1 !important; } } #customNewsModal .custom-news-hidden { display: none !important; } #customNewsModal .custom-news-title-input { margin-top: 10px; } #customNewsModal .custom-news-params-top { margin-top: 16px; } #customNewsModal .custom-news-error { margin-top: 16px; padding: 12px 14px; border-radius: 10px; border: 1px solid var(--negative-general); background: var(--negative-transparent); color: var(--negative-general); line-height: 1.45; } #customNewsModal .custom-news-label { margin: 0; } #customNewsModal .custom-news-input { background-color: var(--elements-hover); color: var(--text-general); border: 1px solid transparent; outline: none; box-shadow: none; border-radius: 6px; padding: 5px 10px; font-size: 16px; font-family: Formula1; transition: background-color 0.15s, color 0.15s, border-color 0.15s; } #customNewsModal textarea.custom-news-input { min-height: 150px; resize: vertical; line-height: 1.5; } #customNewsModal .custom-news-input::placeholder { color: var(--text-hover); } #customNewsModal .custom-news-input:hover { background-color: var(--new-primary-background-dropdown); } #customNewsModal .custom-news-input:focus { background-color: var(--new-primary-background-dropdown); color: var(--text-selected); } #customNewsModal .custom-news-switch { display: flex; align-items: center; gap: 10px; min-height: 36px; } #customNewsModal .custom-news-switch .form-check-input { margin-top: 0; } #customNewsModal .custom-news-switch .form-check-label { color: var(--text-general); } #customNewsModal .custom-news-checkbox { display: flex; align-items: center; gap: 8px; color: var(--text-general); cursor: pointer; min-height: 22px; } #customNewsModal .custom-news-checkbox .form-check-input { margin-top: 0; flex: 0 0 auto; } #customNewsModal .custom-news-checkbox .form-check-input:checked{ background-color: var(--new-primary); border-color: var(--new-primary); } #customNewsModal .custom-news-checkbox-label { font-family: Formula1Bold; } #customNewsModal .custom-news-number-control { display: flex; align-items: center; gap: 8px; } #customNewsModal .custom-news-number-input { flex: 1; min-width: 0; text-align: center; } #customNewsModal .custom-news-number-control.disabled { opacity: 0.55; } #customNewsModal .custom-news-number-control.disabled .custom-news-step-button { pointer-events: none; } #customNewsModal .custom-news-step-button { border: none; color: inherit; padding: 0; } #customNewsModal .custom-news-params { margin-top: 14px; display: flex; flex-direction: column; gap: 16px; } #customNewsModal .custom-news-help { color: var(--text-hover); font-size: 13px; line-height: 1.45; } #customNewsDateInput::-webkit-calendar-picker-indicator { filter: invert(1); opacity: 0.9; } #customNewsModal .custom-news-info { padding: 10px 12px; border-radius: 10px; background: var(--elements-hover); color: var(--text-hover); font-size: 13px; line-height: 1.45; } #customNewsModal .custom-news-preview-frame { display: flex; align-items: center; justify-content: center; min-height: 190px; padding: 12px; border-radius: 12px; border: 1px solid var(--elements-hover); background: linear-gradient(180deg, var(--elements-hover), var(--elements-color)); } #customNewsModal .custom-news-preview-frame img { width: 100%; max-height: 260px; object-fit: cover; border-radius: 10px; box-shadow: 0 12px 24px rgba(0, 0, 0, 0.18); } #customNewsModal .custom-news-preview-frame.is-empty { color: var(--text-hover); font-size: 13px; text-align: center; } #customNewsModal .dropdown-global { width: 100%; display: block; } #customNewsModal .redesigned-dropdown { justify-content: space-between; gap: 10px; background-color: var(--elements-hover); } #customNewsModal .redesigned-dropdown:hover{ background-color: var(--new-primary-background-dropdown) !important; } #customNewsModal .redesigned-dropdown .dropdown-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #customNewsModal .redesigned-dropdown-menu { width: 100%; max-width: none; max-height: 250px; overflow-y: auto; min-width: 285px; } #customNewsModal .redesigned-dropdown-menu::-webkit-scrollbar, #customNewsModal .custom-news-image-grid::-webkit-scrollbar { width: 4px; } #customNewsModal .redesigned-dropdown-menu::-webkit-scrollbar-thumb, #customNewsModal .custom-news-image-grid::-webkit-scrollbar-thumb { background-color: var(--white-general); border-radius: 3px; } #customNewsModal .redesigned-dropdown-menu::-webkit-scrollbar-thumb:hover, #customNewsModal .custom-news-image-grid::-webkit-scrollbar-thumb:hover { background-color: var(--scrollbar-hover); } #customNewsTypeMenu .redesigned-dropdown-item{ padding-left: 14px; } .custom-dropdown-section-title { padding: 4px 8px; color: var(--text-secondary); font-size: 16px; overflow: hidden; } .custom-dropdown-section-title::marker { display: none; color: transparent; } #customNewsModal .custom-news-image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: 12px; max-height: 330px; overflow-y: auto; padding-right: 4px; } #customNewsModal .custom-news-image-card { appearance: none; display: flex; flex-direction: column; gap: 8px; width: 100%; padding: 0px; height: 70px; border: 1px solid transparent; border-radius: 12px; background: var(--elements-hover); color: var(--text-general); text-align: left; cursor: pointer; overflow: hidden; } #customNewsModal .custom-news-image-card:hover img { transform: translateY(-2px) scale(1.04); filter: brightness(1.13); } #customNewsModal .custom-news-image-card.selected { background: var(--elements-color); border-color: var(--new-primary); box-shadow: 0 0 0 1px rgba(225, 6, 0, 0.22); } #customNewsModal .custom-news-image-card:focus-visible { outline: none; border-color: var(--new-primary); box-shadow: 0 0 0 2px rgba(225, 6, 0, 0.22); } #customNewsModal .custom-news-image-card img { width: 100%; aspect-ratio: 16 / 10; object-fit: cover; border-radius: 9px; background: var(--background-general); height: 100%; } #customNewsModal .custom-news-image-caption { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-hover); font-size: 12px; } @media (max-width: 767px) { #customNewsModal .modal-footer.custom-footer { flex-direction: column-reverse; } #customNewsModal .modal-footer.custom-footer .close-modal, #customNewsModal .modal-footer.custom-footer .confirm-modal { width: 100%; } } .turning-point-div { display: flex; flex-direction: row; align-items: center; gap: 7px; } .turning-point-div .bi { font-size: 18px; } .tp-button { border-radius: 5px; padding: 1px 5px; border: 1px solid transparent; display: flex; flex-direction: row; align-items: center; } .tp-button-selected { border: none !important; cursor: default !important; width: 100px !important; justify-content: center; } .tp-result-span { opacity: 0; animation: fadeAppearLeft 0.2s forwards 0.05s; } .tp-button:not(.tp-button-selected):hover { transform: scale(1.04); } .tp-button:not(.tp-button-selected):active { transform: scale(0.96); } .cancel-tp { color: var(--negative-general); background-color: color-mix(in srgb, var(--elements) 60%, var(--negative-general)); cursor: pointer; border: 1px solid var(--negative-general); } .approve-tp { color: var(--positive-general); background-color: color-mix(in srgb, var(--elements) 60%, var(--positive-general)); cursor: pointer; border: 1px solid var(--positive-general); } .random-tp { color: var(--random-general); background-color: color-mix(in srgb, var(--elements) 60%, var(--random-general)); cursor: pointer; border: 1px solid var(--random-general); } .tech-grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; background-size: 40px 40px; background-image: linear-gradient(to right, var(--tech-grid-line) 1px, transparent 1px), linear-gradient(to bottom, var(--tech-grid-line) 1px, transparent 1px); mask-image: radial-gradient(circle at center, black 40%, transparent 80%); -webkit-mask-image: radial-gradient(circle at center, black 40%, transparent 80%); } .tech-grid::after { content: ""; position: absolute; inset: 0; pointer-events: none; background-size: inherit; background-image: linear-gradient(to right, var(--tech-grid-highlight-color) 1px, transparent 1px), linear-gradient(to bottom, var(--tech-grid-highlight-color) 1px, transparent 1px); opacity: var(--tech-grid-highlight-opacity); mask-image: radial-gradient(circle var(--tech-grid-highlight-radius) at var(--glow-x) var(--glow-y), rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 20%, rgba(0, 0, 0, 0) 70%); -webkit-mask-image: radial-gradient(circle var(--tech-grid-highlight-radius) at var(--glow-x) var(--glow-y), rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 1) 20%, rgba(0, 0, 0, 0) 70%); } .glow-spot { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 600px; height: 600px; background-color: var(--new-primary); border-radius: 50%; filter: blur(120px); opacity: 0.1; z-index: 0; pointer-events: none; transition: opacity 0.15s ease; } .glow-spot--off { opacity: 0; } /* --- UTILIDADES TEXTO --- */ .text-primary { color: var(--new-primary); } .upload-desc { color: var(--text-muted); height: 30px; } .upload-desc input { background-color: transparent; border: none; color: var(--new-primary); text-decoration: underline; outline: none; cursor: pointer; } .font-mono { font-family: monospace; } /* --- NAVBAR --- */ .navbar { position: relative; z-index: 10; width: 100%; max-width: 1280px; /* max-w-7xl */ margin: 0 auto; padding: 1.5rem; display: flex; justify-content: space-between; align-items: center; } .nav-brand { display: flex; align-items: center; gap: 0.5rem; font-size: 1.125rem; font-weight: 600; letter-spacing: -0.025em; } .nav-brand span span { color: #64748b; } .nav-links { display: flex; gap: 1.5rem; font-size: 0.875rem; font-weight: 500; } .nav-links a:hover { color: var(--new-primary); } /* --- MAIN CONTAINER --- */ .main-container { position: relative; z-index: 10; flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 0 1rem; text-align: center; } /* --- HERO SECTION --- */ .hero-section { max-width: 56rem; align-items: center; display: flex; flex-direction: column; margin: 0 auto 1rem auto; opacity: 0; transform: translateY(20px); animation: fadeIn 0.8s ease-out forwards; } .badge-version { display: inline-block; padding: 0.25rem 0.75rem; border-radius: 9999px; background-color: var(--new-primary-transparent); border: 1px solid var(--new-primary); color: var(--new-primary); font-size: 0.75rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; cursor: pointer; margin-bottom: 1.5rem; transition: background-color 0.15s, color 0.15s; } .badge-version:hover { background-color: var(--new-primary); color: var(--white-general); } .badge-version:active { transform: scale(0.96); } .hero-title { font-size: 3rem; /* fallback mobile */ font-weight: 900; white-space: nowrap; text-transform: uppercase; line-height: 0.9; text-shadow: 0 0 40px rgba(200, 158, 252, 0.2); color: var(--hero-title); margin-bottom: 1.5rem; text-align: center; font-family: 'Formula1Dark'; } @media (min-width: 768px) { .hero-title { font-size: 5.5rem; } } #statusTitle { color: var(--text-general) !important; } .gradient-text { background: linear-gradient(135deg, var(--hero-title) 30%, var(--new-primary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .hero-desc { color: var(--text-muted); font-size: 1.125rem; font-weight: 300; max-width: 42rem; display: flex; flex-direction: column; justify-content: center; align-items: center; margin: 0 auto; text-align: center; line-height: 1.6; } .fake-text-and-animated-container { position: relative; height: 28px; } #animatedText { white-space: pre; text-align: left; height: 28px; display: inline-block; position: absolute; left: 0px; top: 0px; color: var(--text-muted); } .fake-text { opacity: 0; height: 28px; pointer-events: none; white-space: nowrap; } #static-text { color: var(--text-muted); } @keyframes pop-in { 0% { opacity: 0; transform: translateY(-11px) scale(0.8); color: var(--text-general); } 50% { opacity: 1; } 100% { opacity: 1; transform: translateY(0) scale(1); color: var(--text-muted); } } #animatedText .char { display: inline-block; opacity: 0; animation: pop-in 0.10s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; } .drop-zone { position: relative; padding: 3rem; transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); overflow: hidden; } .select-file-input { position: relative; display: inline-block; width: 100%; height: 100%; color: var(--new-primary); } .file-input-hidden { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 20; } .socials-container { display: flex; flex-direction: row; justify-content: center; align-items: center; gap: 25px; margin-top: 4rem; color: var(--text-muted); } .socials-container a { color: var(--text-muted); text-decoration: none; transition: color 0.15s; } .socials-container a.bi-custom-patreon::before { background-color: var(--text-muted); } .icon-circle { width: 5rem; height: 5rem; border-radius: 50%; background-color: color-mix(in srgb, var(--new-primary) 25%, var(--background)); display: flex; align-items: center; justify-content: center; margin-bottom: 1rem; transition: background-color 0.15s ease, transform 0.15s ease; } .icon-circle.success-mode { background-color: color-mix(in srgb, var(--positive-general) 25%, var(--background)) !important; } .idle-content { display: flex; flex-direction: column; align-items: center; } .icon-circle.success-mode i { color: var(--positive-general) !important; } .icon-scale-0 { transform: scale(0) !important; } .icon-circle i { font-size: 3.1rem; padding-bottom: 4px; } #statusIcon { transition: transform 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275), color 0.15s ease; display: flex; justify-content: center; align-items: center; transform: scale(1); } .bi-lock#statusIcon { font-size: 2.8rem; color: var(--new-secondary); } .drop-zone h3 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; transition: color 0.15s; } .tags-container { display: flex; gap: 1rem; font-size: 0.75rem; color: #475569; /* slate-600 */ font-family: monospace; } .tag { background-color: #0f172a; /* slate-900 */ padding: 0.25rem 0.5rem; border-radius: 0.25rem; border: 1px solid #1e293b; /* slate-800 */ } /* Estados: Cargando y Éxito */ .overlay-state { display: none; /* Oculto por defecto */ position: absolute; top: 0; left: 0; right: 0; bottom: 0; flex-direction: column; align-items: center; justify-content: center; border-radius: 1rem; z-index: 30; } .overlay-state.visible { display: flex; } .loading-bg { background-color: rgba(19, 19, 31, 0.9); backdrop-filter: blur(8px); } .success-bg { background-color: rgba(19, 19, 31, 0.95); backdrop-filter: blur(8px); } /* Spinner */ .spinner { width: 4rem; height: 4rem; border: 4px solid rgba(200, 158, 252, 0.3); border-top-color: var(--new-primary); border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 1rem; } .drag-active { border-color: var(--new-primary); /* Un borde sólido morado */ color: var(--new-primary); box-shadow: inset 0 0 60px 7px var(--new-primary); } /* --- Spinner Personalizado --- */ .loading-spinner { width: 50px; height: 50px; border: 5px solid var(--new-primary-transparent); border-radius: 50%; border-top-color: var(--new-primary); opacity: 0; position: absolute; transition: opacity 0.15s ease-in-out; } .loading-spinner.show { animation: spin 1s ease-in-out infinite; opacity: 1; } .save-file-button { text-decoration: none !important; color: var(--new-primary); background-color: transparent; padding: 3px 5px; border-radius: 5px; transition: background-color 0.15s, color 0.15s; } .save-file-button:hover { text-decoration: none; background-color: var(--new-primary-transparent); } .save-file-button:active { transform: scale(0.96); } .recents-container { display: flex; flex-direction: column; align-items: center; } .recent-label { font-size: 18px; color: var(--text-general); } .recents-list { display: flex; flex-direction: row; gap: 10px; flex-wrap: wrap; justify-content: center; } .recent-file { display: flex; flex-direction: row; align-items: center; gap: 3px; } .file-name:hover { background-color: color-mix(in srgb, var(--new-primary) 25%, var(--background)); } .file-name:active { transform: scale(0.96); } .file-name { color: var(--new-primary); font-size: 14px; cursor: pointer; background-color: transparent; padding: 3px 5px; border-radius: 5px; transition: background-color 0.15s, color 0.15s; text-decoration: underline; } .last-opened-time { font-size: 12px; cursor: pointer; color: var(--text-muted); background-color: transparent; padding: 3px 5px; border-radius: 5px; min-width: 67px; text-align: center; transition: color 0.15s, background-color 0.15s; } .last-opened-time:hover { color: var(--negative-general); background-color: color-mix(in srgb, var(--elements) 75%, var(--negative-general)); } @keyframes spin { to { transform: rotate(360deg); } } .success-icon-bg { background-color: var(--positive-transparent); /* Tu variable */ width: 4rem; height: 4rem; margin-bottom: 1rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 1rem auto; } /* --- ANIMACIONES --- */ @keyframes fadeIn { to { opacity: 1; transform: translateY(0); } } @keyframes pulse-glow { 0%, 100% { box-shadow: 0 0 15px var(--new-primary-glow); border-color: var(--new-primary); } 50% { box-shadow: 0 0 30px var(--new-primary-glow); border-color: #e0c4ff; } } .redesigned-dropdown { display: flex; align-items: center; background: transparent; outline: none; border: none; border-radius: 6px; color: var(--text-general); gap: 10px; transform: rotate(0deg); transition: background-color 0.15s, color 0.15s; padding: 5px 10px; } .redesigned-dropdown i { transform: rotate(0deg); transition: transform 0.15s; } .redesigned-dropdown.open i { transform: rotate(180deg); } .redesigned-dropdown:hover, .redesigned-dropdown.open { background-color: var(--new-primary-background-dropdown); color: var(--white-general); } .redesigned-dropdown:hover .redesigned-chevron, .redesigned-dropdown.open .redesigned-chevron { background-color: var(--white-general); } .redesigned-dropdown-menu { background-color: var(--dropdown-back); border: none; box-shadow: var(--shadow-m); border-radius: 10px; margin-top: 5px; padding: 5px; position: absolute; z-index: 100; transition: opacity 0.15s, transform 0.15s; } .dropdown-global { position: relative; display: inline-block; } #teamMenu .redesigned-dropdown-item, .team-select-menu .redesigned-dropdown-item { padding: 2px 8px 2px 0px !important; } .team-select-menu .redesigned-dropdown-item{ min-width: 230px; } #objectiveMenu { min-width: 280px; } #attributeMenu{ min-width: 200px; } #staffMenu{ min-width: 220px; } .redesigned-dropdown-item { padding: 4px 8px; border-radius: 7px; cursor: pointer; text-decoration: none; color: var(--text-general); transition: background-color 0.15s, color 0.15s; display: flex; align-items: center; justify-content: space-between; min-width: 100px; gap: 8px; } .redesigned-dropdown-item:hover { background-color: var(--dropdown-hover); color: var(--white-general); } .custom-news-driver-option { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; } .custom-news-driver-option-logo { width: 18px; height: 18px; object-fit: contain; flex: 0 0 18px; } .custom-news-driver-option-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .dropdown-global:has(.redesigned-dropdown:not(.open)) .redesigned-dropdown-menu { opacity: 0; transform: translateY(-10px); pointer-events: none; } .dropdown-global:has(.redesigned-dropdown.open) .redesigned-dropdown-menu { transform: translateY(0); pointer-events: auto; } /*svg image*/ .redesigned-chevron { mask: url('/assets/images/redesigned-chevron.svg') no-repeat center; mask-size: contain; background-color: var(--text-general); transition: background-color 0.15s, transform 0.15s; width: 12px; height: 12px; } .redesigned-chevron.clicked{ transform: rotate(180deg); } #teamMenu { padding-right: 0px; width: 250px; } #seasonObjectiveInput { width: 40px; } .team-viewer .custom-input-number { padding: 5px 8px; text-align: left; font-size: 18px; } .pit-crew-middle { border-left: 2px solid var(--separator-light); border-radius: 0; border-right: 2px solid var(--separator-light); padding: 0 12px; } .pit-crew-group .one-stat-panel { padding: 0 12px; border-radius: 0; } .team-viewer .bar-container { height: auto; } .unit-and-input { display: flex; flex-direction: row; align-items: center; padding: 0 0px 0 10px; border-radius: 7px; } .unit-label { color: var(--text-secondary) } .gauge-container { position: relative; height: 50px; font-family: sans-serif; } .gauge-svg { width: 100%; height: 100%; transform: rotate(135deg); } .gauge-circle { fill: none; stroke-width: 8; stroke-linecap: round; } .gauge-bg { stroke: var(--separator-light); stroke-dasharray: 212 283; } .gauge-progress { stroke: var(--positive-general); transition: stroke-dashoffset 0.15s ease; stroke-dasharray: 212 283; stroke-dashoffset: calc(212 - (212 * var(--perc) / 100)); } .gauge-indicator{ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 14px; color: var(--positive-general); font-family: "Formula1Bold"; } .gauge-and-buttons{ display: flex; flex-direction: row; align-items: center; gap: 12px; margin-left: 15px; } .gauge-and-buttons .input-buttons{ flex-direction: column; gap: 6px; } .gauge-and-buttons .input-buttons i:hover{ color: var(--positive-general); } .facility .stats-header-separator{ height: 90%; } .facility-group{ width: 100%; } /*margin top top facility group but not the first one*/ .facility-group:not(:first-child){ margin-top: 20px; } .script-view.hide .facility-group{ pointer-events: none; } .script-view.hide *{ pointer-events: none; } .facilities-pills a::before{ z-index: 0; } .facilities-pills .basic-label{ z-index: 1; } .facilities-pills a:not(.active) .basic-label{ color: var(--text-secondary); } .facilities-pills a:hover .basic-label{ color: var(--text-general); } .unit-measure{ color: var(--text-tertiary); } .new-augment-button.bi-chevron-left, .new-augment-button.bi-chevron-right{ font-size: 14px; width: 18px; height: 18px; padding-top: 1px; } .new-augment-button.bi-chevron-left{ padding-right: 2px; } .new-augment-button.bi-chevron-right{ padding-left: 2px; } .news-options-global{ position: absolute; right: 10px; top: 10px; /* display: flex; */ text-align: center; z-index: 200; } .news-options{ background-color: var(--elements); border-radius: 5px; width: 24px; height: 24px; color: var(--text-general); cursor: pointer; } .news-options:hover{ color: var(--new-primary); } .news-options:active{ transform: scale(0.96); } .news-options i{ pointer-events: none; } .news-options-menu{ background-color: var(--dropdown-back); border-radius: 10px; box-shadow: var(--shadow-m); position: absolute; z-index: 200; margin-top: 5px; padding: 5px; width: max-content; right: 0; opacity: 0; pointer-events: none; transform: translateY(-10px); transition: opacity 0.15s, transform 0.15s; } .news-options-context{ margin-top: 0px; border-top: 0 solid color-mix(in srgb, var(--text-general) 12%, transparent); padding: 0; max-height: 0; opacity: 0; overflow: hidden; transform: translateY(-6px); pointer-events: none; transition: max-height 0.25s ease, opacity 0.15s ease, transform 0.25s ease, padding 0.25s ease, margin-top 0.25s ease; } .news-options-context.open{ padding: 0px; margin-top: 5px; max-height: 220px; opacity: 1; transform: translateY(0); pointer-events: auto; } .news-options-context-textarea{ width: 300px; height: 110px; resize: none; background: transparent; border: 1px solid color-mix(in srgb, var(--text-general) 18%, transparent); border-radius: 10px; padding: 10px; color: var(--text-general); outline: none; font: inherit; } .news-options-context-textarea:focus{ border-color: color-mix(in srgb, var(--new-primary) 60%, transparent); } .news-options-context-actions{ display: flex; justify-content: flex-end; gap: 10px; margin-top: 5px; } .news-options-global:has(.news-options.active) .news-options-menu{ opacity: 1; pointer-events: auto; transform: translateY(0); } .difficulty-options{ display: flex; flex-direction: column; gap: 10px; width: 100%; } .one-difficulty{ display: flex; flex-direction: column; align-items: flex-start; gap: 5px; } .dif-title{ display: flex; flex-direction: row; align-items: center; gap: 5px; width: 100%; } .dif-buttons{ display: flex; flex-direction: row; gap: 3px; } .dif-title .avg-separator{ background-color: var(--disabled-text); width: 1px; } .dif-status{ font-size: 13px; flex-grow: 1; } .dif-description{ font-size: 13px; } .dif-status.disabled{ color: var(--text-tertiary); } #contractModal .modal-subtitle{ color: var(--text-secondary); margin: 0; font-size: 16px; } .junior-driver-right{ margin-left: 10px; } #juniorTeamContractMenu{ max-height: 300px; min-width: 400px; overflow-y: auto; } #juniorTeamContractMenu a{ font-size: 16px; justify-content: flex-start; } .junior-team-drivers-list{ display: flex; flex-direction: column; margin-top: 10px; } .contract-categories{ display: flex; flex-direction: row; gap: 8px; align-items: center; margin-left: 10px; flex-grow: 1; } .contract-categories .contract-category{ padding: 2px 5px; border-radius: 5px; background-color: transparent; color: var(--text-tertiary); font-size: 14px; cursor: pointer; transition: background-color 0.15s, color 0.15s; } .contract-categories .contract-category.active{ background-color: var(--new-primary-transparent); color: var(--new-primary); } .contract-categories .contract-category:hover{ background-color: var(--new-primary-transparent); color: var(--new-primary); } .contract-categories .contract-category:active{ transform: scale(0.96); } /* customizable snowflake styling */ .snowflake { color: #fff; font-size: 1em; font-family: Arial, sans-serif; text-shadow: 0 0 5px #000; } .snowflake,.snowflake .inner{animation-iteration-count:infinite;animation-play-state:running}@keyframes snowflakes-fall{0%{transform:translateY(0)}100%{transform:translateY(110vh)}}@keyframes snowflakes-shake{0%,100%{transform:translateX(0)}50%{transform:translateX(80px)}}.snowflake{position:fixed;top:-10%;z-index:9999;-webkit-user-select:none;user-select:none;cursor:default;pointer-events:none;animation-name:snowflakes-shake;animation-duration:3s;animation-timing-function:ease-in-out}.snowflake .inner{animation-duration:10s;animation-name:snowflakes-fall;animation-timing-function:linear}.snowflake:nth-of-type(0){left:1%;animation-delay:0s}.snowflake:nth-of-type(0) .inner{animation-delay:0s}.snowflake:first-of-type{left:10%;animation-delay:1s}.snowflake:first-of-type .inner,.snowflake:nth-of-type(8) .inner{animation-delay:1s}.snowflake:nth-of-type(2){left:20%;animation-delay:.5s}.snowflake:nth-of-type(2) .inner,.snowflake:nth-of-type(6) .inner{animation-delay:6s}.snowflake:nth-of-type(3){left:30%;animation-delay:2s}.snowflake:nth-of-type(11) .inner,.snowflake:nth-of-type(3) .inner{animation-delay:4s}.snowflake:nth-of-type(4){left:40%;animation-delay:2s}.snowflake:nth-of-type(10) .inner,.snowflake:nth-of-type(4) .inner{animation-delay:2s}.snowflake:nth-of-type(5){left:50%;animation-delay:3s}.snowflake:nth-of-type(5) .inner{animation-delay:8s}.snowflake:nth-of-type(6){left:60%;animation-delay:2s}.snowflake:nth-of-type(7){left:70%;animation-delay:1s}.snowflake:nth-of-type(7) .inner{animation-delay:2.5s}.snowflake:nth-of-type(8){left:80%;animation-delay:0s}.snowflake:nth-of-type(9){left:90%;animation-delay:1.5s}.snowflake:nth-of-type(9) .inner{animation-delay:3s}.snowflake:nth-of-type(10){left:25%;animation-delay:0s}.snowflake:nth-of-type(11){left:65%;animation-delay:2.5s} /* BENTO GRID STYLES */ .main-viewer-section:has(.season-review-bento:not(.d-none)), .main-viewer-section:has(.session-results-table:not(.d-none)) { background-color: transparent; box-shadow: none; } .season-review-bento { display: flex; width: 100%; height: calc(100vh - 140px); align-items: center; justify-content: center; } .bento-grid { display: grid; width: 100%; height: 100%; grid-template-columns: repeat(8, 1fr); grid-template-rows: repeat(5, 1fr); gap: 12px; padding: 2px 8px; color: var(--text-general); } .bento-item { background-color: var(--superficials); display: flex; flex-direction: column; padding: 12px 15px; justify-content: flex-start; gap: 8px; border-radius: 10px; box-shadow: var(--shadow-m); outline: 2px solid transparent; transition: outline-color 0.15s ease, background-color 0.15s ease; /* min-height: 120px; */ } .item-1:hover, .item-2:hover, .item-3:hover, .item-5:hover, .item-6:hover, .item-7:hover, .item-8:hover, .item-9:hover, .item-10:hover { outline-color: var(--new-primary); background-color: color-mix(in srgb, var(--new-primary-transparent) 50%, var(--superficials)) } .item-1:active, .item-2:active, .item-3:active, .item-5:active, .item-6:active, .item-7:active, .item-8:active, .item-9:active, .item-10:active { transform: scale(0.98); } /* Layout específico de cada bloque */ .item-1 { grid-column: span 2; grid-row: span 4; cursor: pointer; } .item-2 { grid-column: span 2; grid-row: span 2; cursor: pointer; } .item-3 { grid-column: span 4; grid-row: span 2; cursor: pointer; } .item-4 { grid-column: span 4; grid-row: span 2; } .item-5 { grid-column: span 2; grid-row: span 2; cursor: pointer; } .item-6 { grid-column: span 1; grid-row: span 1; cursor: pointer; } .item-7 { grid-column: span 2; grid-row: span 1; cursor: pointer; } .item-8 { grid-column: span 2; grid-row: span 1; cursor: pointer; } .item-9 { grid-column: span 2; grid-row: span 1; cursor: pointer; } .item-10 { grid-column: span 1; grid-row: span 1; cursor: pointer; } @media (min-height: 1080px) { .item-6, .item-7, .item-8, .item-9, .item-10 { height: fit-content; } .bento-split-list{ flex-grow: 0; } .bento-standings-half, .item-6 .wins-drivers-list, .item-10 .wins-drivers-list{ justify-content: flex-start; gap: 8px; } } .bento-grid .stats-header-separator{ background-color: var(--text-tertiary); } .bento-title{ font-size: 18px; color: var(--text-general); display: flex; align-items: baseline; gap: 5px; } .bento-subtitle{ font-size: 16px; font-family: "Formula1"; color: var(--text-tertiary); } .bento-driver-standings{ display: flex; flex-direction: column; width: 100%; justify-content: space-between; height: 100%; } .bento-team-standings{ display: flex; flex-direction: column; width: 100%; justify-content: space-between; height: 100%; } .bento-split-list{ display: flex; flex-direction: row; width: 100%; gap: 10px; flex-grow: 1; } .bento-split-list .bento-standings-half + .bento-standings-half{ border-left: 2px solid var(--text-tertiary); padding-left: 12px; } .bento-standings-half{ display: flex; flex-direction: column; width: 100%; justify-content: space-between; } .season-review-driver{ display: flex; flex-direction: row; align-items: center; gap: 8px; } .season-review-driver:has(.season-review-driver-position.champion) .season-review-driver-position{ border-left: 2px solid #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .season-review-driver:has(.season-review-driver-position.champion) .season-review-driver-points:not(.diff){ border-right: 2px solid #FEDB37; background: linear-gradient(270deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .season-review-driver-logo-div{ width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .season-review-driver-logo-div .drivers-table-logo{ width: 24px; height: 24px; object-fit: contain; } .season-review-driver-name{ flex-grow: 1; color: var(--text-general); } .season-review-driver-position{ font-family: "NumbersBold"; width: 25px; text-align: center; padding-top: 1px; height: 22px; } .season-review-driver-points{ font-family: "NumbersBold"; width: 52px; text-align: center; padding-top: 1px; height: 22px; } .season-review-driver-points.diff{ font-family: "NumbersFont"; color: var(--text-tertiary); margin-right: -15px; } #standings2ndhalf{ border-left: 1px solid var(--separator-light); padding-left: 15px; } #standings1sthalf{ padding-right: 5px; } .season-review-driver-name span:not(.bold-font){ color: var(--text-secondary); } .season-review-driver-name span.bold-font{ text-transform: uppercase; } .season-review-team{ display: flex; flex-direction: row; align-items: center; gap: 8px; height: 22px; } .season-review-team:has(.season-review-team-position.champion) .season-review-team-position{ border-left: 2px solid #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .season-review-team:has(.season-review-team-position.champion) .season-review-team-points:not(.diff){ border-right: 2px solid #FEDB37; background: linear-gradient(270deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .season-review-team-logo-div{ width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; } .season-review-team-logo-div .drivers-table-logo{ width: 24px; height: 24px; object-fit: contain; } .season-review-team-name{ flex-grow: 1; text-transform: uppercase; color: var(--text-general); } .season-review-team-position{ font-family: "NumbersBold"; width: 25px; text-align: center; height: 22px; } .season-review-team-points{ font-family: "NumbersBold"; width: 52px; text-align: center; height: 22px; } .season-review-team-points.diff{ font-family: "NumbersFont"; color: var(--text-tertiary); margin-right: -15px; } #teamStandings{ width: 100%; } .bento-comparison-container{ display: flex; flex-direction: row; gap: 9px; align-items: flex-start; justify-content: space-between; } .bento-sub-item-comparison{ display: flex; flex-direction: row; align-items: flex-start; width: 50%; gap: 4px; } .bento-qualifying-container{ display: flex; flex-direction: row; gap: 12px; align-items: flex-start; justify-content: space-between; } .bento-sub-item-qualifying{ display: flex; flex-direction: row; align-items: flex-start; width: 33.333%; } .bento-sub-item-qualifying .bento-vertical-label{ font-size: 20px; } .poles-comparison, .q3-comparison, .q2-comparison{ display: flex; flex-direction: column; width: 100%; } .poles-comparison.with-scrollbar, .q3-comparison.with-scrollbar, .q2-comparison.with-scrollbar, .bento-driver-standings.with-scrollbar, .bento-team-standings.with-scrollbar, .race-comparison.with-scrollbar, .quali-comparison.with-scrollbar { padding-right: 6px; } .bento-vertical-label{ writing-mode: vertical-rl; text-orientation: sideways; transform: rotate(180deg); color: var(--text-tertiary); font-size: 28px; letter-spacing: 1px; user-select: none; line-height: 1; } .bento-subtitle{ color: var(--text-tertiary); } .race-comparison{ display: flex; flex-direction: column; width: 100%; justify-content: space-between; padding: 2px; } .quali-comparison{ display: flex; flex-direction: column; width: 100%; justify-content: space-between; padding: 2px; } .season-review-comparison-row{ display: flex; align-items: center; gap: 8px; width: 100%; height: 22px; border-radius: 4px; outline: 1px solid transparent; padding: 0 6px; cursor: pointer; } .season-review-comparison-row.fe:hover{ outline-color: var(--ferrari-primary); background-color: color-mix(in srgb, var(--ferrari-primary) 12%, var(--superficials)); } .season-review-comparison-row.mc:hover{ outline-color: var(--mclaren-primary); background-color: color-mix(in srgb, var(--mclaren-primary) 12%, var(--superficials)); } .season-review-comparison-row.rb:hover{ outline-color: var(--redbull-primary); background-color: color-mix(in srgb, var(--redbull-primary) 12%, var(--superficials)); } .season-review-comparison-row.me:hover{ outline-color: var(--mercedes-primary); background-color: color-mix(in srgb, var(--mercedes-primary) 12%, var(--superficials)); } .season-review-comparison-row.al:hover{ outline-color: var(--alpine-primary); background-color: color-mix(in srgb, var(--alpine-primary) 12%, var(--superficials)); } .season-review-comparison-row.wi:hover{ outline-color: var(--williams-primary); background-color: color-mix(in srgb, var(--williams-primary) 12%, var(--superficials)); } .season-review-comparison-row.ha:hover{ outline-color: var(--haas-primary); background-color: color-mix(in srgb, var(--haas-primary) 12%, var(--superficials)); } .season-review-comparison-row.at:hover{ outline-color: var(--alphatauri-primary); background-color: color-mix(in srgb, var(--alphatauri-primary) 12%, var(--superficials)); } .season-review-comparison-row.af:hover{ outline-color: var(--alfa-primary); background-color: color-mix(in srgb, var(--alfa-primary) 12%, var(--superficials)); } .season-review-comparison-row.as:hover{ outline-color: var(--aston-primary); background-color: color-mix(in srgb, var(--aston-primary) 12%, var(--superficials)); } .season-review-comparison-row.ct:hover{ outline-color: var(--custom-team-primary); background-color: color-mix(in srgb, var(--custom-team-primary) 12%, var(--superficials)); } .season-review-comparison-row:active{ transform: scale(0.98); } .season-review-comparison-name{ flex: 1; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; color: var(--text-general); } .season-review-comparison-name.right{ text-align: right; } .season-review-comparison-score{ font-family: "NumbersFont"; width: 26px; text-align: center; padding-top: 3px; font-size: 14px; } .season-review-qualifying-row{ display: flex; align-items: center; width: 100%; height: 22px; gap: 4px; } .season-review-qualifying-position{ font-family: "NumbersBold"; width: 28px; text-align: center; font-size: 14px; padding-top: 3px; height: 22px; } .season-review-qualifying-name{ flex: 1; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-transform: uppercase; color: var(--text-general); } .season-review-qualifying-count{ font-family: "NumbersFont"; width: 26px; text-align: center; padding-top: 3px; font-size: 14px; height: 22px; } .wins-drivers-list{ width: 100%; display: flex; flex-direction: column; } .bento-item.item-10 .wins-drivers-list, .bento-item.item-6 .wins-drivers-list{ justify-content: space-between; height: 100%; } .bento-item.item-5 .wins-drivers-list{ gap: 3px; } .season-review-wins-row{ display: flex; align-items: center; width: 100%; height: 22px; gap: 4px; } .season-review-wins-row.phantom-row{ visibility: hidden; pointer-events: none; } .rounds-counter{ margin-left: auto; padding-right: 8px; color: var(--text-tertiary); font-family: "Formula1"; } .first-number{ padding-right: 2px; } .round-number{ font-family: "NumbersFont"; width: 18px; text-align: center; font-size: 16px; padding-top: 3px; height: 22px; } .season-review-wins-position{ font-family: "NumbersBold"; width: 18px; text-align: center; font-size: 14px; padding-top: 3px; height: 22px; } .season-review-wins-name{ flex: 1; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: var(--text-general); } .season-review-wins-name .bold-font{ text-transform: uppercase; } .season-review-wins-name span:not(.bold-font){ color: var(--text-secondary); } .season-review-wins-count{ font-family: "NumbersFont"; width: 26px; text-align: center; padding-top: 3px; font-size: 14px; height: 22px; margin-left: auto; } .podiums-teams-list .season-review-wins-position, .poles-teams-list .season-review-wins-position, .bento-split-list .season-review-wins-position{ width: 18px; } .podiums-teams-list .season-review-wins-name, .poles-teams-list .season-review-wins-name, .item-8 .bento-split-list .season-review-wins-name{ max-width: 135px; } .item-7 .bento-split-list .season-review-wins-name, .item-9 .bento-split-list .season-review-wins-name{ max-width: 135px; } .session-results-rows{ display: flex; flex-direction: column; gap: 0; color: var(--text-general); } .session-results-title{ font-family: 'Formula1Dark' !important; text-transform: uppercase; display: flex; flex-wrap: wrap; align-items: center; column-gap: 10px; padding: 16px 20px 12px 18px; letter-spacing: -0.3px; color: var(--text-general); position: relative; overflow: hidden; row-gap: 0px; background-color: var(--generals); box-shadow: var(--shadow-s); border-top-right-radius: 10px; border-top-left-radius: 10px; } #sessionResultsEditToggle{ cursor: pointer; } #sessionResultsCompactToggle{ cursor: pointer; } .session-results-table.is-compact{ width: 55vw; margin-left: auto; margin-right: auto; } .session-results-title::before{ content: ''; position: absolute; top: 0; bottom: 0; left: 0; width: var(--session-results-flag-window-width, 280px); background-image: var(--session-results-flag-bg, none); background-repeat: no-repeat; background-position: left center; background-size: var(--session-results-flag-width, 280px) auto; opacity: 0.5; -webkit-mask-image: linear-gradient(90deg, #000 0%, transparent 100%); mask-image: linear-gradient(90deg, #000 0%, transparent 100%); -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat; -webkit-mask-size: 100% 100%; mask-size: 100% 100%; pointer-events: none; z-index: 0; } .session-results-title.session-results-title-tall-flag::before{ background-size: var(--session-results-flag-width, 280px) 130%; } .session-results-title > *{ position: relative; z-index: 1; } .session-results-title-main{ order: 1; font-size: 24px; line-height: 1; } .session-results-title-session{ order: 2; font-family: 'Formula1Dark'; font-size: 24px; line-height: 1; color: var(--text-tertiary); } .session-results-title-track{ order: 4; flex: 0 0 100%; margin-top: 2px; font-family: 'Formula1'; font-size: 14px; line-height: 0.6; color: var(--text-general); } .session-results-title-round{ order: 3; margin-left: auto; font-family: 'Formula1Dark'; font-size: 16px; line-height: 1; color: var(--text-secondary); letter-spacing: -0.2px; } .session-results-footer{ display: flex; align-items: center; gap: 10px; padding: 10px 20px 10px 18px; background-color: var(--generals); box-shadow: inset 0 1px 0 rgba(255,255,255,0.06); text-transform: uppercase; border-bottom-right-radius: 10px; border-bottom-left-radius: 10px; } .session-results-footer-label{ font-family: 'NumbersBold'; font-size: 16px; color: color-mix(in srgb, #B14DFF 70%, var(--text-general)); flex: 0 0 auto; padding-top: 3px; line-height: 1; } .session-results-footer-right{ margin-left: auto; color: var(--text-secondary); } .session-results-footer-time{ font-family: 'NumbersFont'; font-size: 16px; color: var(--text-general); flex: 0 0 auto; color: color-mix(in srgb, #B14DFF 70%, var(--text-general)); transform: translateY(2px); } .session-results-footer .session-results-team{ border-right: 2px solid var(--text-tertiary); padding-right: 11px; } .session-results-row{ display: grid; grid-template-columns: var(--session-results-col-drag, 0px) 42px var(--session-results-col-grid, 46px) 310px 340px 1fr var(--session-results-col-q1, 0px) var(--session-results-col-q2, 0px) var(--session-results-col-q3, 0px) var(--session-results-col-laps, 0px) var(--session-results-col-fl, 100px) var(--session-results-col-time, 130px) var(--session-results-col-quali-grid, 0px) var(--session-results-col-points, 70px); align-items: center; background-color: var(--table-default); column-gap: 0px; } .session-results-cell{ margin: 0 4px; } .session-results-cell.hidden{ margin: 0; } .session-results-row > .session-results-cell:first-child{ margin-left: 0; } .session-results-row > .session-results-cell:last-child{ margin-right: 0; } .session-results-table.is-edit{ --session-results-col-drag: 22px; } .session-results-drag{ display: flex; align-items: center; justify-content: center; padding: 0 !important; } .session-results-drag-handle{ cursor: grab; } .session-results-drag-handle:active{ cursor: grabbing; } .session-results-drag-icon{ width: 12px; height: 18px; background: radial-gradient(circle, var(--text-tertiary) 1.2px, transparent 1.3px) 0 0/6px 6px; opacity: 0.8; } .session-results-row.dragging{ opacity: 0.7; } .session-results-time-input{ width: 100%; min-width: 0; border: 1px solid rgba(255,255,255,0.12); background: rgba(0,0,0,0.2); color: var(--text-general); border-radius: 6px; padding: 0px 4px; font-family: 'NumbersFont', 'Formula1', sans-serif; font-size: 14px; text-align: center; height: 20px; } .session-results-time-input:disabled{ opacity: 0.6; } .session-results-table.is-quali{ --session-results-col-q1: 100px; --session-results-col-q2: 100px; --session-results-col-q3: 100px; --session-results-col-fl: 0px; --session-results-col-time: 0px; } .session-results-table.is-quali.has-quali-grid{ --session-results-col-quali-grid: 58px; } .session-results-table.is-practice{ --session-results-col-laps: 70px; --session-results-col-points: 0px; } .session-results-table.no-points{ --session-results-col-points: 0px; } .session-results-table.no-grid{ --session-results-col-grid: 0px; } .session-results-table.no-time{ --session-results-col-time: 0px; } .session-results-row > div.hidden{ padding: 0 !important; overflow: hidden; visibility: hidden; pointer-events: none; margin: 0; } .session-results-gained-lost.hidden{ padding: 0 !important; overflow: hidden; visibility: hidden; pointer-events: none; } .session-results-time.hidden{ padding: 0 !important; overflow: hidden; visibility: hidden; pointer-events: none; } .session-results-row.quali-cut-q2, .session-results-row.quali-cut-q3{ border-bottom: 2px solid var(--f1-first); } .session-results-row.odd { background-color: var(--table-default-odd); } .session-results-row > div { padding: 4px 8px; min-width: 0; } @media (max-height: 950px) { .session-results-row > div { padding: 2px 8px; } .changes-grid{ gap: 15px !important; } .recommended-downloads{ margin-top: -10px !important; font-size: 14px !important; } .mod-name.title-2026{ margin-top: -50px !important; } } .session-results-row:hover { background-color: var(--session-results-hover); } .session-results-row:hover .session-results-driver-name, .session-results-row:hover .session-results-engine-name, .session-results-row:hover .session-results-fastest-lap:not(.fastest), .session-results-row:hover .session-results-time, .session-results-row:hover .session-results-laps, .session-results-row:hover .session-results-quali-lap:not(.fastest){ color: var(--text-general); } .session-results-position { font-family: 'NumbersBold'; font-size: 16px; height: 32px; display: flex; align-items: center; justify-content: center; padding-top: 7px !important; margin-left: 0px; } .session-results-gained-lost { font-family: 'NumbersFont'; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 2px; line-height: 1; padding-top: 7px !important; } .session-results-gained-lost .bi-caret-up-fill, .bi-caret-down-fill, .standings-pos-change .bi-caret-up-fill, .standings-pos-change .bi-caret-down-fill { font-size: 10px; } .session-results-gained-lost.up { color: var(--positive-general); } .session-results-gained-lost.down { color: var(--negative-general); } .session-results-gained-lost.neutral { color: var(--text-general); } .session-results-driver-name { display: flex; align-items: center; gap: 4px; white-space: nowrap; min-width: 0; } .session-results-first-name { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-transform: capitalize; min-width: 0; } .session-results-surname { text-transform: uppercase; flex: 0 0 auto; color: var(--text-general); } .session-results-dotd { color: var(--positive-general); font-family: "Formula1Bold"; font-size: 12px; margin-left: 6px; flex: 0 0 auto; } .session-results-nationality-flag{ width: 24px; height: 16px; object-fit: cover; border-radius: 4px; flex: 0 0 auto; margin-right: 4px; } .session-results-team { display: flex; align-items: center; gap: 8px; min-width: 0; } .session-results-team-name { font-family: "Formula1Bold"; color: var(--text-general); text-transform: uppercase; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .session-results-engine-name { color: var(--text-secondary); text-transform: uppercase; font-family: "Formula1"; font-size: 13px; white-space: nowrap; flex: 0 0 auto; margin-left: 6px; } .session-results-spacer { padding: 0 !important; } .session-results-fastest-lap, .session-results-laps, .session-results-quali-lap, .session-results-time { color: var(--text-secondary); } .session-results-quali-lap.session-results-q3{ color: var(--text-general); } .session-results-row.leader .session-results-time { color: var(--text-general); } .session-results-row.leader .session-results-fastest-lap:not(.fastest) { color: var(--text-general); } .session-results-fastest-lap.session-results-fastest-lap-practice:not(.fastest) { color: var(--text-general); } .session-results-fastest-lap, .session-results-laps, .session-results-quali-lap, .session-results-time{ font-family: 'NumbersFont'; font-size: 14px; display: flex; align-items: center; justify-content: center; text-align: center; padding-top: 7px !important; } .session-results-points{ font-family: 'NumbersBold'; font-size: 16px; height: 32px; display: flex; align-items: center; justify-content: center; text-align: center; padding-top: 7px !important; } .session-results-grid-position { display: flex; align-items: center; justify-content: center; text-align: center; font-size: 14px; white-space: nowrap; color: var(--text-secondary); font-family: 'NumbersFont'; } .session-results-grid-position.penalty { color: var(--f1-first); } .session-results-points.positive { color: var(--positive-general); } .session-results-row:has(.session-results-position.champion) .session-results-position { border-left: 2px solid #FEDB37; color: #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .session-results-row.odd:has(.session-results-position.champion) .session-results-position { color: #FEDB37; background: linear-gradient(90deg, rgba(254, 219, 55, 0.35) 0%, transparent 75%); } .session-results-row:has(.session-results-position.second) .session-results-position { border-left: 2px solid #C0C0C0; background: linear-gradient(90deg, rgba(192, 192, 192, 0.35) 0%, transparent 75%); } .session-results-row.odd:has(.session-results-position.second) .session-results-position { background: linear-gradient(90deg, rgba(192, 192, 192, 0.35) 0%, transparent 75%); } .session-results-row:has(.session-results-position.third) .session-results-position { border-left: 2px solid #CD7F32; background: linear-gradient(90deg, rgba(205, 127, 50, 0.35) 0%, transparent 75%); } .session-results-row.odd:has(.session-results-position.third) .session-results-position { background: linear-gradient(90deg, rgba(205, 127, 50, 0.35) 0%, transparent 75%); } .session-results-fastest-lap.fastest, .session-results-quali-lap.fastest { border-left: 2px solid #B14DFF; background: linear-gradient(90deg, rgba(177, 77, 255, 0.35) 0%, transparent 75%); text-decoration: none; color: color-mix(in srgb, #B14DFF 70%, var(--text-general)); } .session-results-driver-logo-div { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0 !important; flex: 0 0 auto; } .session-results-driver-logo-div img, .session-results-driver-logo-div div { width: 24px; height: 24px; object-fit: contain; } ================================================ FILE: src/themes.css ================================================ :root { /* GENERAL COLORS */ --hero-title: #eeeeee; --background: #111111; --generals: #1e1e1e; --superficials: #222222; --dropdown-back: #313131; --elements: #303030; --augment-buttons-bg: #474747; --augment-buttons-hover: #5a5a5a; --dropdown-hover: #414141; --table-default: #1b1b1b; --table-default-odd: #1e1e1e; --table-header: #2f2f2f; --dark-text: #b1b1b1; --disabled-text: #7a7a7a; --modal-text: #777777; --text-muted: #94a3b8; --negative-text: #292929; --new-primary: #c89efc; /* C37CF9 alternative*/ --new-primary-dark: #ba82ff; --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --text-selected: #e7d3ff; --text-selected-secondary: #b1dbd6; --new-secondary: #9AD2CB; --new-secondary-transparent: color-mix(in srgb, var(--new-secondary) 25%, var(--elements)); --new-secondary-dark: #74aaa4; --table-hover: #4a4a4a; --elements-hover: #3a3a3a; --box-shadow: #00000033; --table-first: #fde06b; --table-first-light: #f0e2ac; --table-second: #b5bcc6; --table-second-light: #cacaca; --table-third: #d7985a; --table-third-light: #f2cda6; --table-dnf: #676767; --text-outline: #000000; --blur-color: #616161d3; --toast-background: #5a5b5c; --console-transparent: #42424299; --separator: #4a4a4a; --separator-light: #4f4f4f; --frozen: #77e2f0; --extra-hard: #edf037; --brutal: #f0c537; --unfair: #f09a37; --insane: #f06b37; --impossible: #f03737; --modal-warning: #f03737a1; --modal-warning-text: #f8bbbb; --engine-table-name: #878787; --engine-table-name-hover: #575757; --support-me: #f96854; --twitter: #51afe9; --reddit: #f87444; --custom-dif: #52ebbd; --discord: #7289da; --table-hover-text: #292929; --session-results-hover: #4a4a4a; --news-overlays-text: #dedde6; --news-date-text: #292929; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ffb38c 100%); --scrollbar-hover: color-mix(in srgb, var(--white-general) 70%, var(--elements)); --amo-1: #bd6962; --amo-2: #eec987; --rate-ok-color: #2ecc71; --rate-ok-bg: color-mix(in srgb, var(--rate-ok-color) 30%, transparent); --rate-warning-color: #f1c40f; --rate-warning-bg: color-mix(in srgb, var(--rate-warning-color) 30%, transparent); --rate-danger-color: #e67e22; --rate-danger-bg: color-mix(in srgb, var(--rate-danger-color) 30%, transparent); --rate-blocked-color: #e74c3c; --rate-blocked-bg: color-mix(in srgb, var(--rate-blocked-color) 30%, transparent); --mentality-enthusiastic: #c055e3; --mentality-positive: #10ffff; --mentality-neutral: #f3f3f3; --mentality-negative: #e9de35; --mentality-demoralized: #dd3356; --technical-chief: #ec4e98; --race-engineer: #bce64a; --head-aero: #5097e7; --sporting-director: #925aeb; --background-general: #110f24; --slight-contrast: #797599; --white-general: #f3f3f3; --tech-grid-color: var(--text-general); --tech-grid-line: color-mix(in srgb, var(--tech-grid-color) 2%, transparent); --glow-x: 50%; --glow-y: 0px; --tech-grid-highlight-color: var(--new-primary); --tech-grid-highlight-opacity: 0.54; --tech-grid-highlight-radius: 240px; --gold-gradient-text: #f3f3f3; --text-general: #dedde6; --text-general-transparent: #e9e8f0be; --logos-general: #f0f0f0; --text-secondary: #a8a8a8; --text-tertiary: #777777; --negative-general: #e95656; --negative-general-clicked: #cf2626; --negative-transparent: color-mix(in srgb, var(--negative-general) 25%, var(--elements)); --positive-general: #5bd999; --positive-transparent: color-mix(in srgb, var(--positive-general) 25%, var(--elements)); --positive-general-clicked: #11ac5e; --positive-general-darker: #3f8a6b; --positive--general-darker2: #2f6a4b; --random-general: #d4ff6f; --h2h-general: #34c297; --graph-general: #34bdc2; --h2h-detail: #88ebcd; --graph-detail: #95ebee; --tools-general: #18152e; --component-general: #3f3c57; --component-general-hover: #4e4b69; --component-general-hover-extra: #5d5a7c; --overall-holder: #21202e; --dropdown-back-general: #8b87af; --active-general: #1a1826; --row-cell: #110D14; --row-odd-cell: #15121B; --special-columns: #464166; --delete-div: #f15353; --weather-vis-back: #292738; --weather-vis-border: #2c2b3b; --h2h-back: #1f1b3b; --h2h-back-hover: #302a57; --active-buttons-back: #5f5a7c; --mid-gray-contrast: #63636e; --awaiting-color: #8a8a8a; --mode-line-edit: #fff7b0; --mode-line-ai: #baffea; --mode-line-view: #e8b6ff; --prob-viewer-hover: #625e86; --custom-modal-back: #393744; --text-in-front: #eeedf3; --background--buttons-hover: #6f698f; --ai-transparent: #baffea99; --edit-transparent: #fff7b099; --view-transparent: #e8b6ff99; --white-gradient: #ffffff99; --f2-border: #38c8f0; --f2-back: #388cd5; --f2-active: #1374fc; --f3-back: #cf2e2e; --f3-border: #ff6956; --f1-first: #ff0000; --shadow-no: 0 1px 2px #00000030, /*dark shadow */ 0 2px 4px #00000015; /*soft shadow */ --shadow-s: inset 0 1px 2px #ffffff30, /*top highlight */ 0 1px 2px #00000030, /*dark shadow */ 0 2px 4px #00000015; /*soft shadow */ --shadow-xs: inset 0 1px 2px #ffffff20, /*top highlight */ 0 1px 2px #00000020, /*dark shadow */ 0 2px 4px #00000010; --shadow-m: inset 0 1px 2px #ffffff50, /*top highlight */ 0 2px 4px #00000030, /*dark shadow */ 0 4px 8px #00000015; /*soft shadow */ --shadow-l: inset 0 1px 2px #ffffff70, /*top highlight */ 0 4px 6px #00000030, /*dark shadow */ 0 6px 110px #00000015; /*soft shadow */ --shadow-red: inset 0 -1px 2px #ff3b3b25, /* light from bottom */ 2px 2px 4px #ff3b3b20, /* red glow bottom-right */ -1px -1px 2px #00000030, /* opposite shadow */ 0 2px 4px #00000020; /* soft depth */ --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); /* TEAMS COLORS */ --alphatauri-original: #5E8FAA; --alphatauri-secondary-original: #f1f1f1; --alpine-original: #F168BA; --alpine-secondary-original: #f1f1f1; --andretti-primary: #fac51c; --andretti-secondary: #1e7696; --lotus-primary: #b09247; --lotus-secondary: #595959; --visarb-primary: #6c8ff3; --visarb-secondary: #f1f1f1; --toyota-primary: #989898; --toyota-secondary: #f1f1f1; --hugo-primary: #bd9514; --hugo-secondary: #f1f1f1; --alphatauri-primary: #5E8FAA; --porsche-primary: #873AC4; --porsche-secondary: #f1f1f1; --brawn-primary: #d0e610; --brawn-secondary: #f1f1f1; --ferrari-primary: #f53311; --general-secondary: #f1f1f1; --mercedes-primary: #00F5D3; --redbull-original: #3172bf; --redbull-secondary-original: #ffd300; --redbull-primary: #3172bf; --redbull-secondary: #ffd300; --ford-primary: #3172bf; --ford-secondary: #f1f1f1; --stake-primary: #54d650; --stake-secondary: #595959; --audi-primary: #C00A26; --audi-secondary: #f1f1f1; --sauber-primary: #f50537; --aston-original: #07977b; --aston-secondary-original: #c3dc00; --aston-primary: #07977b; --aston-secondary: #c3dc00; --racingpoint-primary: #F395C7; --racingpoint-secondary: #f1f1f1; --jordan-primary: #e6e624; --jordan-secondary: #1f1f1f; --alfa-original: #C92D4B; --alfa-secondary-original: #f1f1f1; --alfa-primary: #C92D4B; --alfa-secondary: #f1f1f1; --alphatauri-secondary: #f1f1f1; --haas-original: #c1c1c7; --haas-secondary-original: #f62039; --haas-primary: #c1c1c7; --haas-secondary: #f62039; --mclaren-primary: #FF8000; --mclaren-secondary: #47c7fc; --williams-original: #1868DB; --williams-secondary-original: #f1f1f1; --williams-primary: #1868DB; --williams-secondary: #f1f1f1; --bmw-primary: #f1f1f1; --bmw-secondary: #1957bb; --alpine-primary: #F168BA; --alpine-secondary: #f1f1f1; --cadillac-primary: #111111; --cadillac-secondary: #d8d8d8; --renault-primary: #f7c82f; --honda-primary: #d80000; --custom-team-primary: #ffffff; --custom-team-secondary: #ffffff; --redbull-primary-transparent: color-mix(in srgb, var(--redbull-primary) 30%, var(--elements)); --redbull-secondary-transparent: color-mix(in srgb, var(--redbull-secondary) 30%, var(--elements)); --redbull-primary-transparent-original: color-mix(in srgb, var(--redbull-original) 30%, var(--elements)); --redbull-secondary-transparent-original: color-mix(in srgb, var(--redbull-secondary-original) 30%, var(--elements)); --ford-primary-transparent: color-mix(in srgb, var(--ford-primary) 18%, var(--elements)); --ford-secondary-transparent: color-mix(in srgb, var(--ford-secondary) 18%, var(--elements)); --ferrari-primary-transparent: color-mix(in srgb, var(--ferrari-primary) 18%, var(--elements)); --general-secondary-transparent: color-mix(in srgb, var(--general-secondary) 18%, var(--elements)); --mercedes-primary-transparent: color-mix(in srgb, var(--mercedes-primary) 18%, var(--elements)); --mclaren-primary-transparent: color-mix(in srgb, var(--mclaren-primary) 18%, var(--elements)); --mclaren-secondary-transparent: color-mix(in srgb, var(--mclaren-secondary) 18%, var(--elements)); --williams-primary-transparent: color-mix(in srgb, var(--williams-primary) 18%, var(--elements)); --williams-primary-transparent-original: color-mix(in srgb, var(--williams-original) 18%, var(--elements)); --williams-secondary-transparent: color-mix(in srgb, var(--williams-secondary) 18%, var(--elements)); --williams-secondary-transparent-original: color-mix(in srgb, var(--williams-secondary-original) 18%, var(--elements)); --bmw-primary-transparent: color-mix(in srgb, var(--bmw-primary) 18%, var(--elements)); --bmw-secondary-transparent: color-mix(in srgb, var(--bmw-secondary) 18%, var(--elements)); --haas-primary-transparent: color-mix(in srgb, var(--haas-primary) 18%, var(--elements)); --haas-secondary-transparent: color-mix(in srgb, var(--haas-secondary) 18%, var(--elements)); --haas-primary-transparent-original: color-mix(in srgb, var(--haas-original) 18%, var(--elements)); --haas-secondary-transparent-original: color-mix(in srgb, var(--haas-secondary-original) 18%, var(--elements)); --alphatauri-primary-transparent: color-mix(in srgb, var(--alphatauri-primary) 18%, var(--elements)); --alphatauri-primary-transparent-original: color-mix(in srgb, var(--alphatauri-original) 18%, var(--elements)); --alphatauri-secondary-transparent: color-mix(in srgb, var(--alphatauri-secondary) 18%, var(--elements)); --alphatauri-secondary-transparent-original: color-mix(in srgb, var(--alphatauri-secondary-original) 18%, var(--elements)); --sauber-primary-transparent: color-mix(in srgb, var(--sauber-primary) 18%, var(--elements)); --alpine-primary-transparent: color-mix(in srgb, var(--alpine-primary) 18%, var(--elements)); --alpine-primary-transparent-original: color-mix(in srgb, var(--alpine-original) 18%, var(--elements)); --alpine-secondary-transparent: color-mix(in srgb, var(--alpine-secondary) 18%, var(--elements)); --alpine-secondary-transparent-original: color-mix(in srgb, var(--alpine-secondary-original) 18%, var(--elements)); --renault-primary-transparent: color-mix(in srgb, var(--renault-primary) 18%, var(--elements)); --cadillac-primary-transparent: color-mix(in srgb, var(--cadillac-primary) 18%, var(--elements)); --cadillac-secondary-transparent: color-mix(in srgb, var(--cadillac-secondary) 18%, var(--elements)); --aston-primary-transparent: color-mix(in srgb, var(--aston-primary) 18%, var(--elements)); --aston-secondary-transparent: color-mix(in srgb, var(--aston-secondary) 18%, var(--elements)); --aston-primary-transparent-original: color-mix(in srgb, var(--aston-original) 18%, var(--elements)); --aston-secondary-transparent-original: color-mix(in srgb, var(--aston-secondary-original) 18%, var(--elements)); --racingpoint-primary-transparent: color-mix(in srgb, var(--racingpoint-primary) 18%, var(--elements)); --racingpoint-secondary-transparent: color-mix(in srgb, var(--racingpoint-secondary) 18%, var(--elements)); --jordan-primary-transparent: color-mix(in srgb, var(--jordan-primary) 18%, var(--elements)); --jordan-secondary-transparent: color-mix(in srgb, var(--jordan-secondary) 18%, var(--elements)); --alfa-primary-transparent: color-mix(in srgb, var(--alfa-primary) 18%, var(--elements)); --alfa-primary-transparent-original: color-mix(in srgb, var(--alfa-original) 18%, var(--elements)); --alfa-secondary-transparent: color-mix(in srgb, var(--alfa-secondary) 18%, var(--elements)); --alfa-secondary-transparent-original: color-mix(in srgb, var(--alfa-secondary-original) 18%, var(--elements)); --audi-primary-transparent: color-mix(in srgb, var(--audi-primary) 18%, var(--elements)); --audi-secondary-transparent: color-mix(in srgb, var(--audi-secondary) 18%, var(--elements)); --toyota-primary-transparent: color-mix(in srgb, var(--toyota-primary) 18%, var(--elements)); --toyota-secondary-transparent: color-mix(in srgb, var(--toyota-secondary) 18%, var(--elements)); --porsche-primary-transparent: color-mix(in srgb, var(--porsche-primary) 18%, var(--elements)); --porsche-secondary-transparent: color-mix(in srgb, var(--porsche-secondary) 18%, var(--elements)); --brawn-primary-transparent: color-mix(in srgb, var(--brawn-primary) 18%, var(--elements)); --brawn-secondary-transparent: color-mix(in srgb, var(--brawn-secondary) 18%, var(--elements)); --stake-primary-transparent: color-mix(in srgb, var(--stake-primary) 18%, var(--elements)); --stake-secondary-transparent: color-mix(in srgb, var(--stake-secondary) 18%, var(--elements)); --visarb-primary-transparent: color-mix(in srgb, var(--visarb-primary) 18%, var(--elements)); --visarb-secondary-transparent: color-mix(in srgb, var(--visarb-secondary) 18%, var(--elements)); --hugo-primary-transparent: color-mix(in srgb, var(--hugo-primary) 18%, var(--elements)); --hugo-secondary-transparent: color-mix(in srgb, var(--hugo-secondary) 18%, var(--elements)); --lotus-primary-transparent: color-mix(in srgb, var(--lotus-primary) 18%, var(--elements)); --lotus-secondary-transparent: color-mix(in srgb, var(--lotus-secondary) 18%, var(--elements)); --andretti-primary-transparent: color-mix(in srgb, var(--andretti-primary) 18%, var(--elements)); --andretti-secondary-transparent: color-mix(in srgb, var(--andretti-secondary) 18%, var(--elements)); --custom-team-primary-transparent: color-mix(in srgb, var(--custom-team-primary) 18%, var(--elements)); --custom-team-secondary-transparent: color-mix(in srgb, var(--custom-team-secondary) 18%, var(--elements)); } body.light-theme { --background: #e4e4e4; --generals: #dadada; --superficials: #d0d0d0; --dropdown-back: #cfcfcf; --elements: #cccccc; --table-default: #f5f5f5; --table-default-odd: #e0e0e0; --dropdown-hover: #bbbbbb; --text-general: #252525; --text-secondary: #666666; --text-tertiary: #999999; --hero-title: #111111; --text-muted: #475569; --logos-general: #333333; --augment-buttons-bg: #bbbbbb; --augment-buttons-hover: #aaaaaa; --white-general: #131313; --tech-grid-color: var(--text-general); --tech-grid-line: color-mix(in srgb, var(--tech-grid-color) 5%, transparent); --dark-text: #444444; --disabled-text: #888888; --modal-text: #666666; --negative-text: #eeeeee; --new-primary: #7a5fd8; --new-primary-dark: #5a42b5; --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --text-selected: #aa88ff; --text-selected-secondary: #55aaa9; --new-secondary: #66aabb; --new-secondary-dark: #447788; --new-secondary-transparent: color-mix(in srgb, var(--new-secondary) 25%, var(--elements)); --table-hover: #cccccc; --elements-hover: #bbbbbb; --box-shadow: #00000022; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); --tech-grid-highlight-color: var(--new-primary); --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --table-first: #c5ad4d; --table-first-light: #f0e2ac; --table-second: #797b80; --table-second-light: #cacaca; --table-third: #b87a3c; --table-third-light: #f2cda6; --table-header: #d1d1d1; --table-dnf: #8b8b8b; --sessions-results-hover: #d1d1d1; --positive-general: #2a9b63; --rate-ok-color: #24a45a; --rate-ok-bg: color-mix(in srgb, var(--rate-ok-color) 30%, transparent); --rate-warning-color: #bca211; --rate-warning-bg: color-mix(in srgb, var(--rate-warning-color) 30%, transparent); --rate-danger-color: #d46a11; --rate-danger-bg: color-mix(in srgb, var(--rate-danger-color) 30%, transparent); --general-secondary: #f1f1f1; --mercedes-primary: #13c2a2; --haas-primary: #57575a; --mentality-enthusiastic: #c055e3; --mentality-positive: #10ffff; --mentality-neutral: #222222; --mentality-negative: #8a8100; --mentality-demoralized: #dd3356; --scrollbar-hover: color-mix(in srgb, var(--white-general) 70%, var(--elements)); --redbull-primary-transparent: color-mix(in srgb, var(--redbull-primary) 30%, var(--elements)); --redbull-secondary-transparent: color-mix(in srgb, var(--redbull-secondary) 30%, var(--elements)); --ferrari-primary-transparent: color-mix(in srgb, var(--ferrari-primary) 18%, var(--elements)); --general-secondary-transparent: color-mix(in srgb, var(--general-secondary) 18%, var(--elements)); --mercedes-primary-transparent: color-mix(in srgb, var(--mercedes-primary) 18%, var(--elements)); --mclaren-primary-transparent: color-mix(in srgb, var(--mclaren-primary) 18%, var(--elements)); --mclaren-secondary-transparent: color-mix(in srgb, var(--mclaren-secondary) 18%, var(--elements)); --williams-primary-transparent: color-mix(in srgb, var(--williams-primary) 18%, var(--elements)); --haas-primary-transparent: color-mix(in srgb, var(--haas-primary) 18%, var(--elements)); --haas-secondary-transparent: color-mix(in srgb, var(--haas-secondary) 18%, var(--elements)); --alphatauri-primary-transparent: color-mix(in srgb, var(--alphatauri-primary) 18%, var(--elements)); --alphatauri-primary-transparent-original: color-mix(in srgb, var(--alphatauri-original) 18%, var(--elements)); --alphatauri-secondary-transparent: color-mix(in srgb, var(--alphatauri-secondary) 18%, var(--elements)); --alphatauri-secondary-transparent-original: color-mix(in srgb, var(--alphatauri-secondary-original) 18%, var(--elements)); --sauber-primary-transparent: color-mix(in srgb, var(--sauber-primary) 18%, var(--elements)); --alpine-primary-transparent: color-mix(in srgb, var(--alpine-primary) 18%, var(--elements)); --alpine-primary-transparent-original: color-mix(in srgb, var(--alpine-original) 18%, var(--elements)); --alpine-secondary-transparent: color-mix(in srgb, var(--alpine-secondary) 18%, var(--elements)); --alpine-secondary-transparent-original: color-mix(in srgb, var(--alpine-secondary-original) 18%, var(--elements)); --renault-primary-transparent: color-mix(in srgb, var(--renault-primary) 18%, var(--elements)); --cadillac-primary-transparent: color-mix(in srgb, var(--cadillac-primary) 18%, var(--elements)); --cadillac-secondary-transparent: color-mix(in srgb, var(--cadillac-secondary) 18%, var(--elements)); --aston-primary-transparent: color-mix(in srgb, var(--aston-primary) 18%, var(--elements)); --aston-secondary-transparent: color-mix(in srgb, var(--aston-secondary) 18%, var(--elements)); --alfa-primary-transparent: color-mix(in srgb, var(--alfa-primary) 18%, var(--elements)); --alfa-primary-transparent-original: color-mix(in srgb, var(--alfa-original) 18%, var(--elements)); --alfa-secondary-transparent: color-mix(in srgb, var(--alfa-secondary) 18%, var(--elements)); --alfa-secondary-transparent-original: color-mix(in srgb, var(--alfa-secondary-original) 18%, var(--elements)); --audi-primary-transparent: color-mix(in srgb, var(--audi-primary) 18%, var(--elements)); --audi-secondary-transparent: color-mix(in srgb, var(--audi-secondary) 18%, var(--elements)); --toyota-primary-transparent: color-mix(in srgb, var(--toyota-primary) 18%, var(--elements)); --toyota-secondary-transparent: color-mix(in srgb, var(--toyota-secondary) 18%, var(--elements)); --porsche-primary-transparent: color-mix(in srgb, var(--porsche-primary) 18%, var(--elements)); --porsche-secondary-transparent: color-mix(in srgb, var(--porsche-secondary) 18%, var(--elements)); --brawn-primary-transparent: color-mix(in srgb, var(--brawn-primary) 18%, var(--elements)); --brawn-secondary-transparent: color-mix(in srgb, var(--brawn-secondary) 18%, var(--elements)); --stake-primary-transparent: color-mix(in srgb, var(--stake-primary) 18%, var(--elements)); --stake-secondary-transparent: color-mix(in srgb, var(--stake-secondary) 18%, var(--elements)); --visarb-primary-transparent: color-mix(in srgb, var(--visarb-primary) 18%, var(--elements)); --visarb-secondary-transparent: color-mix(in srgb, var(--visarb-secondary) 18%, var(--elements)); --hugo-primary-transparent: color-mix(in srgb, var(--hugo-primary) 18%, var(--elements)); --hugo-secondary-transparent: color-mix(in srgb, var(--hugo-secondary) 18%, var(--elements)); --lotus-primary-transparent: color-mix(in srgb, var(--lotus-primary) 18%, var(--elements)); --lotus-secondary-transparent: color-mix(in srgb, var(--lotus-secondary) 18%, var(--elements)); --andretti-primary-transparent: color-mix(in srgb, var(--andretti-primary) 18%, var(--elements)); --andretti-secondary-transparent: color-mix(in srgb, var(--andretti-secondary) 18%, var(--elements)); --custom-team-primary-transparent: color-mix(in srgb, var(--custom-team-primary) 18%, var(--elements)); --custom-team-secondary-transparent: color-mix(in srgb, var(--custom-team-secondary) 18%, var(--elements)); } body.og-theme { --background: #110f24; --generals: #18152e; --superficials: #1f1b3b; --dropdown-back: #5f5c77; --elements: #323046; --dropdown-hover: #706c8b; --dark-text: #d3d3e7; --disabled-text: #a8a8c3; --modal-text: #9797b8; --negative-text: #3c3c5b; --new-primary: #8f89b8; --new-primary-dark: #6a678f; --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --text-selected: #e7d3ff; --text-selected-secondary: #ff8f63; --new-secondary: #ff7c47; --new-secondary-dark: #d76a3b; --new-secondary-transparent: color-mix(in srgb, var(--new-secondary) 25%, var(--elements)); --table-hover: #50577c; --elements-hover: #3a3852; --box-shadow: #00000044; --footer-og: #797599; --separator: #5f5c77; --table-dnf: #7d7a93; --text-secondary: #8d88b4; --text-tertiary: #757197; --switch-border-on: var(--new-secondary); --switch-border-off: var(--text-general); --tech-grid-color: transparent; --tech-grid-line: transparent; --augment-buttons-bg: #5f5c77; --augment-buttons-hover: #64617d; --scrollbar-hover: color-mix(in srgb, var(--white-general) 70%, var(--elements)); --session-results-hover: #4a4a4a; --table-default: #494561; --table-default-odd: #3c3952; --table-header: #252335; --tech-grid-highlight-color: var(--new-primary); --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --redbull-primary-transparent: color-mix(in srgb, var(--redbull-primary) 30%, var(--elements)); --redbull-secondary-transparent: color-mix(in srgb, var(--redbull-secondary) 30%, var(--elements)); --ferrari-primary-transparent: color-mix(in srgb, var(--ferrari-primary) 18%, var(--elements)); --general-secondary-transparent: color-mix(in srgb, var(--general-secondary) 18%, var(--elements)); --mercedes-primary-transparent: color-mix(in srgb, var(--mercedes-primary) 18%, var(--elements)); --mclaren-primary-transparent: color-mix(in srgb, var(--mclaren-primary) 18%, var(--elements)); --mclaren-secondary-transparent: color-mix(in srgb, var(--mclaren-secondary) 18%, var(--elements)); --williams-primary-transparent: color-mix(in srgb, var(--williams-primary) 18%, var(--elements)); --haas-primary-transparent: color-mix(in srgb, var(--haas-primary) 18%, var(--elements)); --haas-secondary-transparent: color-mix(in srgb, var(--haas-secondary) 18%, var(--elements)); --alphatauri-primary-transparent: color-mix(in srgb, var(--alphatauri-primary) 18%, var(--elements)); --alphatauri-primary-transparent-original: color-mix(in srgb, var(--alphatauri-original) 18%, var(--elements)); --alphatauri-secondary-transparent: color-mix(in srgb, var(--alphatauri-secondary) 18%, var(--elements)); --alphatauri-secondary-transparent-original: color-mix(in srgb, var(--alphatauri-secondary-original) 18%, var(--elements)); --sauber-primary-transparent: color-mix(in srgb, var(--sauber-primary) 18%, var(--elements)); --alpine-primary-transparent: color-mix(in srgb, var(--alpine-primary) 18%, var(--elements)); --alpine-primary-transparent-original: color-mix(in srgb, var(--alpine-original) 18%, var(--elements)); --alpine-secondary-transparent: color-mix(in srgb, var(--alpine-secondary) 18%, var(--elements)); --alpine-secondary-transparent-original: color-mix(in srgb, var(--alpine-secondary-original) 18%, var(--elements)); --renault-primary-transparent: color-mix(in srgb, var(--renault-primary) 18%, var(--elements)); --cadillac-primary-transparent: color-mix(in srgb, var(--cadillac-primary) 18%, var(--elements)); --cadillac-secondary-transparent: color-mix(in srgb, var(--cadillac-secondary) 18%, var(--elements)); --aston-primary-transparent: color-mix(in srgb, var(--aston-primary) 18%, var(--elements)); --aston-secondary-transparent: color-mix(in srgb, var(--aston-secondary) 18%, var(--elements)); --aston-primary-transparent-original: color-mix(in srgb, var(--aston-original) 18%, var(--elements)); --aston-secondary-transparent-original: color-mix(in srgb, var(--aston-secondary-original) 18%, var(--elements)); --racingpoint-primary-transparent: color-mix(in srgb, var(--racingpoint-primary) 18%, var(--elements)); --racingpoint-secondary-transparent: color-mix(in srgb, var(--racingpoint-secondary) 18%, var(--elements)); --jordan-primary-transparent: color-mix(in srgb, var(--jordan-primary) 18%, var(--elements)); --jordan-secondary-transparent: color-mix(in srgb, var(--jordan-secondary) 18%, var(--elements)); --alfa-primary-transparent: color-mix(in srgb, var(--alfa-primary) 18%, var(--elements)); --alfa-primary-transparent-original: color-mix(in srgb, var(--alfa-original) 18%, var(--elements)); --alfa-secondary-transparent: color-mix(in srgb, var(--alfa-secondary) 18%, var(--elements)); --alfa-secondary-transparent-original: color-mix(in srgb, var(--alfa-secondary-original) 18%, var(--elements)); --audi-primary-transparent: color-mix(in srgb, var(--audi-primary) 18%, var(--elements)); --audi-secondary-transparent: color-mix(in srgb, var(--audi-secondary) 18%, var(--elements)); --toyota-primary-transparent: color-mix(in srgb, var(--toyota-primary) 18%, var(--elements)); --toyota-secondary-transparent: color-mix(in srgb, var(--toyota-secondary) 18%, var(--elements)); --porsche-primary-transparent: color-mix(in srgb, var(--porsche-primary) 18%, var(--elements)); --porsche-secondary-transparent: color-mix(in srgb, var(--porsche-secondary) 18%, var(--elements)); --brawn-primary-transparent: color-mix(in srgb, var(--brawn-primary) 18%, var(--elements)); --brawn-secondary-transparent: color-mix(in srgb, var(--brawn-secondary) 18%, var(--elements)); --stake-primary-transparent: color-mix(in srgb, var(--stake-primary) 18%, var(--elements)); --stake-secondary-transparent: color-mix(in srgb, var(--stake-secondary) 18%, var(--elements)); --visarb-primary-transparent: color-mix(in srgb, var(--visarb-primary) 18%, var(--elements)); --visarb-secondary-transparent: color-mix(in srgb, var(--visarb-secondary) 18%, var(--elements)); --hugo-primary-transparent: color-mix(in srgb, var(--hugo-primary) 18%, var(--elements)); --hugo-secondary-transparent: color-mix(in srgb, var(--hugo-secondary) 18%, var(--elements)); --lotus-primary-transparent: color-mix(in srgb, var(--lotus-primary) 18%, var(--elements)); --lotus-secondary-transparent: color-mix(in srgb, var(--lotus-secondary) 18%, var(--elements)); --andretti-primary-transparent: color-mix(in srgb, var(--andretti-primary) 18%, var(--elements)); --andretti-secondary-transparent: color-mix(in srgb, var(--andretti-secondary) 18%, var(--elements)); --custom-team-primary-transparent: color-mix(in srgb, var(--custom-team-primary) 18%, var(--elements)); --custom-team-secondary-transparent: color-mix(in srgb, var(--custom-team-secondary) 18%, var(--elements)); } body.vaporwave-theme { --background: #0d0221; --generals: #1a0942; --superficials: #230b55; --elements: #301070; --dropdown-back: #2a0f63; --dropdown-hover: #43208e; --elements-hover: #5329b5; --text-general: #f7dfff; --text-secondary: #cdb7ff; --text-tertiary: #9a82d8; --white-general: #ffffff; --new-primary: #ff5ec3; --new-primary-dark: #e94eb1; --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-secondary: #00b3ff; --new-secondary-dark: #0090d1; --new-secondary-transparent: color-mix(in srgb, var(--new-secondary) 25%, var(--elements)); --text-selected: #ff9fff; --text-selected-secondary: #6ef9ff; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); --table-default: #250d58;; --table-default-odd: #2a0f61; --table-header: #3a1488; --table-dnf: #614ab1; --augment-buttons-bg: #3a1488; --augment-buttons-hover: #4b1da3; --table-hover: #44238e; --box-shadow: #00000066; --separator: #5b36b8; --positive-general: #5dfbc0; --negative-general: #ff477e; --news-overlays-text: #fae6ff; --news-date-text: #0d0221; --primary-gradient: linear-gradient(to right, var(--new-primary), var(--new-secondary)); --scrollbar-hover: color-mix(in srgb, var(--white-general) 70%, var(--elements)); --session-results-hover: #3a1488; --tech-grid-highlight-color: var(--new-primary); --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --redbull-primary-transparent: color-mix(in srgb, var(--redbull-primary) 30%, var(--elements)); --redbull-secondary-transparent: color-mix(in srgb, var(--redbull-secondary) 30%, var(--elements)); --ferrari-primary-transparent: color-mix(in srgb, var(--ferrari-primary) 18%, var(--elements)); --general-secondary-transparent: color-mix(in srgb, var(--general-secondary) 18%, var(--elements)); --mercedes-primary-transparent: color-mix(in srgb, var(--mercedes-primary) 18%, var(--elements)); --mclaren-primary-transparent: color-mix(in srgb, var(--mclaren-primary) 18%, var(--elements)); --mclaren-secondary-transparent: color-mix(in srgb, var(--mclaren-secondary) 18%, var(--elements)); --williams-primary-transparent: color-mix(in srgb, var(--williams-primary) 18%, var(--elements)); --haas-primary-transparent: color-mix(in srgb, var(--haas-primary) 18%, var(--elements)); --haas-secondary-transparent: color-mix(in srgb, var(--haas-secondary) 18%, var(--elements)); --alphatauri-primary-transparent: color-mix(in srgb, var(--alphatauri-primary) 18%, var(--elements)); --alphatauri-primary-transparent-original: color-mix(in srgb, var(--alphatauri-original) 18%, var(--elements)); --alphatauri-secondary-transparent: color-mix(in srgb, var(--alphatauri-secondary) 18%, var(--elements)); --alphatauri-secondary-transparent-original: color-mix(in srgb, var(--alphatauri-secondary-original) 18%, var(--elements)); --sauber-primary-transparent: color-mix(in srgb, var(--sauber-primary) 18%, var(--elements)); --alpine-primary-transparent: color-mix(in srgb, var(--alpine-primary) 18%, var(--elements)); --alpine-primary-transparent-original: color-mix(in srgb, var(--alpine-original) 18%, var(--elements)); --alpine-secondary-transparent: color-mix(in srgb, var(--alpine-secondary) 18%, var(--elements)); --alpine-secondary-transparent-original: color-mix(in srgb, var(--alpine-secondary-original) 18%, var(--elements)); --renault-primary-transparent: color-mix(in srgb, var(--renault-primary) 18%, var(--elements)); --cadillac-primary-transparent: color-mix(in srgb, var(--cadillac-primary) 18%, var(--elements)); --cadillac-secondary-transparent: color-mix(in srgb, var(--cadillac-secondary) 18%, var(--elements)); --aston-primary-transparent: color-mix(in srgb, var(--aston-primary) 18%, var(--elements)); --aston-secondary-transparent: color-mix(in srgb, var(--aston-secondary) 18%, var(--elements)); --aston-primary-transparent-original: color-mix(in srgb, var(--aston-original) 18%, var(--elements)); --aston-secondary-transparent-original: color-mix(in srgb, var(--aston-secondary-original) 18%, var(--elements)); --racingpoint-primary-transparent: color-mix(in srgb, var(--racingpoint-primary) 18%, var(--elements)); --racingpoint-secondary-transparent: color-mix(in srgb, var(--racingpoint-secondary) 18%, var(--elements)); --jordan-primary-transparent: color-mix(in srgb, var(--jordan-primary) 18%, var(--elements)); --jordan-secondary-transparent: color-mix(in srgb, var(--jordan-secondary) 18%, var(--elements)); --alfa-primary-transparent: color-mix(in srgb, var(--alfa-primary) 18%, var(--elements)); --alfa-primary-transparent-original: color-mix(in srgb, var(--alfa-original) 18%, var(--elements)); --alfa-secondary-transparent: color-mix(in srgb, var(--alfa-secondary) 18%, var(--elements)); --alfa-secondary-transparent-original: color-mix(in srgb, var(--alfa-secondary-original) 18%, var(--elements)); --audi-primary-transparent: color-mix(in srgb, var(--audi-primary) 18%, var(--elements)); --audi-secondary-transparent: color-mix(in srgb, var(--audi-secondary) 18%, var(--elements)); --toyota-primary-transparent: color-mix(in srgb, var(--toyota-primary) 18%, var(--elements)); --toyota-secondary-transparent: color-mix(in srgb, var(--toyota-secondary) 18%, var(--elements)); --porsche-primary-transparent: color-mix(in srgb, var(--porsche-primary) 18%, var(--elements)); --porsche-secondary-transparent: color-mix(in srgb, var(--porsche-secondary) 18%, var(--elements)); --brawn-primary-transparent: color-mix(in srgb, var(--brawn-primary) 18%, var(--elements)); --brawn-secondary-transparent: color-mix(in srgb, var(--brawn-secondary) 18%, var(--elements)); --stake-primary-transparent: color-mix(in srgb, var(--stake-primary) 18%, var(--elements)); --stake-secondary-transparent: color-mix(in srgb, var(--stake-secondary) 18%, var(--elements)); --visarb-primary-transparent: color-mix(in srgb, var(--visarb-primary) 18%, var(--elements)); --visarb-secondary-transparent: color-mix(in srgb, var(--visarb-secondary) 18%, var(--elements)); --hugo-primary-transparent: color-mix(in srgb, var(--hugo-primary) 18%, var(--elements)); --hugo-secondary-transparent: color-mix(in srgb, var(--hugo-secondary) 18%, var(--elements)); --lotus-primary-transparent: color-mix(in srgb, var(--lotus-primary) 18%, var(--elements)); --lotus-secondary-transparent: color-mix(in srgb, var(--lotus-secondary) 18%, var(--elements)); --andretti-primary-transparent: color-mix(in srgb, var(--andretti-primary) 18%, var(--elements)); --andretti-secondary-transparent: color-mix(in srgb, var(--andretti-secondary) 18%, var(--elements)); --custom-team-primary-transparent: color-mix(in srgb, var(--custom-team-primary) 18%, var(--elements)); --custom-team-secondary-transparent: color-mix(in srgb, var(--custom-team-secondary) 18%, var(--elements)); } /* Same as default, but swap primary and secondary accent colors. */ body.nightly-theme { --new-primary: #9AD2CB; --new-primary-dark: #74aaa4; --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-secondary: #c89efc; --new-secondary-dark: #ba82ff; --new-secondary-transparent: color-mix(in srgb, var(--new-secondary) 25%, var(--elements)); --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); --text-selected: #b1dbd6; --text-selected-secondary: #e7d3ff; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ffb38c 100%); --tech-grid-highlight-color: var(--new-primary); } body.ferrari-theme { --background: #130c0d; --generals: #1a1012; --superficials: #211417; --dropdown-back: #3b252a; --elements: #2e1c20; --augment-buttons-bg: #4a2c33; --augment-buttons-hover: #5d3740; --dropdown-hover: #4b2f36; --table-default: #191012; --table-default-odd: #1f1316; --table-header: #342026; --dark-text: #b7afb1; --disabled-text: #7f7478; --modal-text: #8f8388; --negative-text: #292929; --new-primary: #ff3d3d; --new-primary-dark: #d12a2a; --new-secondary: #ffb74d; --new-secondary-dark: #d49034; --text-selected: #ffd3d3; --text-selected-secondary: #ffe0ba; --table-hover: #59343c; --elements-hover: #3b252b; --box-shadow: #0000003a; --separator: #5a3a42; --separator-light: #69444d; --background-general: #140d10; --tools-general: #181012; --component-general: #4f2f37; --component-general-hover: #5d3740; --component-general-hover-extra: #6b404a; --overall-holder: #28181d; --dropdown-back-general: #96606d; --active-general: #1f1115; --row-cell: #160d10; --row-odd-cell: #1b1115; --special-columns: #5f3943; --h2h-back: #2a1720; --h2h-back-hover: #3a202b; --active-buttons-back: #6b404a; --prob-viewer-hover: #6a3f4a; --custom-modal-back: #3f2c33; --background--buttons-hover: #7b4855; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ff8f6b 100%); --session-results-hover: #4a2c33; --table-dnf: #7f7478; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #cbbdc1; --text-tertiary: #a89299; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.redbull-theme { --background: #090f1e; --generals: #10172b; --superficials: #132142; --dropdown-back: #263963; --elements: #1e2f52; --augment-buttons-bg: #324874; --augment-buttons-hover: #3e5b8e; --dropdown-hover: #2c426b; --table-default: #101a33; --table-default-odd: #12203d; --table-header: #22365d; --dark-text: #afbdd8; --disabled-text: #7887a8; --modal-text: #8797bb; --negative-text: #292929; --new-primary: #4b7bff; --new-primary-dark: #345fd4; --new-secondary: #ffd24a; --new-secondary-dark: #cfa41f; --text-selected: #d8e5ff; --text-selected-secondary: #ffe593; --table-hover: #344d7f; --elements-hover: #2a406b; --box-shadow: #00000045; --separator: #39578f; --separator-light: #4668a4; --background-general: #0c1429; --tools-general: #11182d; --component-general: #355181; --component-general-hover: #406093; --component-general-hover-extra: #4b6fa7; --overall-holder: #192642; --dropdown-back-general: #6188c0; --active-general: #10192f; --row-cell: #0e162b; --row-odd-cell: #111c34; --special-columns: #3b588b; --h2h-back: #142446; --h2h-back-hover: #1c3260; --active-buttons-back: #4b6fa7; --prob-viewer-hover: #44679e; --custom-modal-back: #253656; --background--buttons-hover: #567ab3; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #8aa7ff 100%); --session-results-hover: #2a406b; --table-dnf: #7887a8; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #c1cce6; --text-tertiary: #92a2c8; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.mercedes-theme { --background: #071316; --generals: #0b1b1f; --superficials: #0e2328; --dropdown-back: #1e3a40; --elements: #153038; --augment-buttons-bg: #234751; --augment-buttons-hover: #2c5b67; --dropdown-hover: #21424a; --table-default: #0b1c20; --table-default-odd: #0d2025; --table-header: #15343b; --dark-text: #a9b9bd; --disabled-text: #6d8086; --modal-text: #7f949a; --negative-text: #292929; --new-primary: #00f5d3; --new-primary-dark: #00cdb1; --new-secondary: #c8f9f2; --new-secondary-dark: #93eadd; --text-selected: #b9fff5; --text-selected-secondary: #e6fffb; --table-hover: #244a54; --elements-hover: #1b3f48; --box-shadow: #00000045; --separator: #2a515c; --separator-light: #336472; --background-general: #09171a; --tools-general: #0d1b1f; --component-general: #2b5560; --component-general-hover: #346574; --component-general-hover-extra: #3d7688; --overall-holder: #10272d; --dropdown-back-general: #5aa6b4; --active-general: #0c1d22; --row-cell: #081417; --row-odd-cell: #0a191d; --special-columns: #2f5b68; --h2h-back: #0e2328; --h2h-back-hover: #14323a; --active-buttons-back: #3d7688; --prob-viewer-hover: #356675; --custom-modal-back: #1a2f34; --background--buttons-hover: #457f92; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #7ff6e4 100%); --session-results-hover: #1b3f48; --table-dnf: #6d8086; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #b8d6d3; --text-tertiary: #85aba6; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.astonmartin-theme { --background: #020706; --generals: #050d0b; --superficials: #07120f; --dropdown-back: #122822; --elements: #0c1b16; --augment-buttons-bg: #215042; --augment-buttons-hover: #2a6654; --dropdown-hover: #17332b; --table-default: #050f0c; --table-default-odd: #06130f; --table-header: #102b22; --dark-text: #a7bdb4; --disabled-text: #6a867b; --modal-text: #7f9e92; --negative-text: #292929; --new-primary: #07977b; --new-primary-dark: #057a63; --new-secondary: #c3dc00; --new-secondary-dark: #9db300; --text-selected: #a9ffe9; --text-selected-secondary: #f2ffb0; --table-hover: #17332b; --elements-hover: #0f241e; --box-shadow: #00000045; --separator: #2a5a4a; --separator-light: #33705b; --background-general: #040b09; --tools-general: #060f0c; --component-general: #1a4036; --component-general-hover: #215246; --component-general-hover-extra: #296458; --overall-holder: #081813; --dropdown-back-general: #6bb5a1; --active-general: #060f0c; --row-cell: #030a08; --row-odd-cell: #040d0a; --special-columns: #214b3f; --h2h-back: #07120f; --h2h-back-hover: #0b1b16; --active-buttons-back: #3b8974; --prob-viewer-hover: #225246; --custom-modal-back: #0b1b16; --background--buttons-hover: #409e82; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #b4d400 100%); --session-results-hover: #0f241e; --table-dnf: #6a867b; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #b9d0c8; --text-tertiary: #89a9a0; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.audi-theme { --background: #0c0708; --generals: #140b0d; --superficials: #1a0f12; --dropdown-back: #31171d; --elements: #231114; --augment-buttons-bg: #472027; --augment-buttons-hover: #5a2831; --dropdown-hover: #3b1c23; --table-default: #12090b; --table-default-odd: #160b0e; --table-header: #2a1318; --dark-text: #c7b7bb; --disabled-text: #8a767b; --modal-text: #a08b90; --negative-text: #292929; --new-primary: #C00A26; --new-primary-dark: #9a071e; --new-secondary: #f1f1f1; --new-secondary-dark: #cfcfcf; --text-selected: #ffd0d8; --text-selected-secondary: #f3f3f3; --table-hover: #4a232b; --elements-hover: #32171c; --box-shadow: #0000003a; --separator: #4a1f27; --separator-light: #5b2730; --background-general: #0e0709; --tools-general: #130a0c; --component-general: #4b222a; --component-general-hover: #5a2831; --component-general-hover-extra: #6a303a; --overall-holder: #1b0d10; --dropdown-back-general: #a96471; --active-general: #140a0d; --row-cell: #0d0708; --row-odd-cell: #11090b; --special-columns: #4f222b; --h2h-back: #1a0f12; --h2h-back-hover: #241317; --active-buttons-back: #6a303a; --prob-viewer-hover: #5a2831; --custom-modal-back: #231114; --background--buttons-hover: #7a3844; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ff6a7f 100%); --session-results-hover: #32171c; --table-dnf: #8a767b; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #d8c6ca; --text-tertiary: #b59ea4; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.vcarb-theme { --background: #070a12; --generals: #0d111d; --superficials: #101827; --dropdown-back: #1f2d4b; --elements: #172033; --augment-buttons-bg: #2c3e63; --augment-buttons-hover: #37507d; --dropdown-hover: #26385a; --table-default: #0c0f1a; --table-default-odd: #0e1321; --table-header: #1a2740; --dark-text: #b8c6de; --disabled-text: #7888a5; --modal-text: #8fa0bf; --negative-text: #292929; --new-primary: #6c8ff3; --new-primary-dark: #4f6fcf; --new-secondary: #f1f1f1; --new-secondary-dark: #cfcfcf; --text-selected: #d8e2ff; --text-selected-secondary: #f3f3f3; --table-hover: #2a3e66; --elements-hover: #1f2c48; --box-shadow: #00000045; --separator: #2b3f6b; --separator-light: #355081; --background-general: #090c16; --tools-general: #0d111d; --component-general: #2f446f; --component-general-hover: #37507d; --component-general-hover-extra: #415d91; --overall-holder: #111a2a; --dropdown-back-general: #6e90cc; --active-general: #0d1220; --row-cell: #080a13; --row-odd-cell: #0a0d18; --special-columns: #314a7a; --h2h-back: #101827; --h2h-back-hover: #16223a; --active-buttons-back: #415d91; --prob-viewer-hover: #37507d; --custom-modal-back: #172033; --background--buttons-hover: #4a69a3; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #b1c4ff 100%); --session-results-hover: #1f2c48; --table-dnf: #7888a5; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #c8d4e8; --text-tertiary: #9fb1cd; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.williams-theme { --background: #050a12; --generals: #09101d; --superficials: #0c1526; --dropdown-back: #162a4d; --elements: #0f1d33; --augment-buttons-bg: #1f3b6b; --augment-buttons-hover: #284a86; --dropdown-hover: #1b3460; --table-default: #070e1a; --table-default-odd: #081122; --table-header: #12284b; --dark-text: #b8cbe4; --disabled-text: #7489aa; --modal-text: #8fa8c8; --negative-text: #292929; --new-primary: #1868DB; --new-primary-dark: #0f52b0; --new-secondary: #f1f1f1; --new-secondary-dark: #cfcfcf; --text-selected: #d7e8ff; --text-selected-secondary: #f3f3f3; --table-hover: #1b3a6a; --elements-hover: #13294b; --box-shadow: #00000045; --separator: #173a70; --separator-light: #1d4586; --background-general: #060c16; --tools-general: #09101d; --component-general: #21427a; --component-general-hover: #284a86; --component-general-hover-extra: #2f579d; --overall-holder: #0c1628; --dropdown-back-general: #5b83c6; --active-general: #081020; --row-cell: #050a12; --row-odd-cell: #060d18; --special-columns: #22447f; --h2h-back: #0c1526; --h2h-back-hover: #0f1f38; --active-buttons-back: #2f579d; --prob-viewer-hover: #284a86; --custom-modal-back: #0f1d33; --background--buttons-hover: #3564b3; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #6fa7ff 100%); --session-results-hover: #13294b; --table-dnf: #7489aa; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #c8d7ee; --text-tertiary: #9fb7d6; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.haas-theme { --background: #0b0b0d; --generals: #121216; --superficials: #17171d; --dropdown-back: #2a2a36; --elements: #1f1f29; --augment-buttons-bg: #343443; --augment-buttons-hover: #414154; --dropdown-hover: #30303f; --table-default: #101013; --table-default-odd: #131318; --table-header: #24242f; --dark-text: #c6c6cf; --disabled-text: #858591; --modal-text: #9b9baa; --negative-text: #292929; --new-primary: #f62039; --new-primary-dark: #cf1a2f; --new-secondary: #c1c1c7; --new-secondary-dark: #9d9da4; --text-selected: #ffd1d7; --text-selected-secondary: #e7e7ea; --table-hover: #343443; --elements-hover: #2a2a36; --box-shadow: #00000045; --separator: #3c3c4c; --separator-light: #48485a; --background-general: #0e0e12; --tools-general: #121216; --component-general: #3b3b4c; --component-general-hover: #47475c; --component-general-hover-extra: #54546b; --overall-holder: #17171d; --dropdown-back-general: #8d8d9e; --active-general: #121216; --row-cell: #0d0d11; --row-odd-cell: #101015; --special-columns: #3a3a4a; --h2h-back: #17171d; --h2h-back-hover: #1f1f29; --active-buttons-back: #54546b; --prob-viewer-hover: #47475c; --custom-modal-back: #1f1f29; --background--buttons-hover: #64647d; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ff7a88 100%); --session-results-hover: #2a2a36; --table-dnf: #858591; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #d1d1da; --text-tertiary: #a7a7b5; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.alpine-theme { --background: #120b11; --generals: #1a101a; --superficials: #221523; --dropdown-back: #3b243e; --elements: #2a182c; --augment-buttons-bg: #4a2c4f; --augment-buttons-hover: #5a3560; --dropdown-hover: #4b2a50; --table-default: #19101a; --table-default-odd: #1f1321; --table-header: #34203a; --dark-text: #cbbdcf; --disabled-text: #8a7b8f; --modal-text: #a08ea7; --negative-text: #292929; --new-primary: #F168BA; --new-primary-dark: #cc4f9a; --new-secondary: #47c7fc; --new-secondary-dark: #2da6d6; --text-selected: #ffe0f2; --text-selected-secondary: #cdefff; --table-hover: #4a2c4f; --elements-hover: #3b2440; --box-shadow: #0000003a; --separator: #5a3a5f; --separator-light: #6a4670; --background-general: #140d14; --tools-general: #181018; --component-general: #5b3a60; --component-general-hover: #6a4570; --component-general-hover-extra: #7a5182; --overall-holder: #281828; --dropdown-back-general: #b77cb8; --active-general: #1f121f; --row-cell: #160d16; --row-odd-cell: #1b111b; --special-columns: #5f3f66; --h2h-back: #221523; --h2h-back-hover: #2f1f33; --active-buttons-back: #7a5182; --prob-viewer-hover: #6a4570; --custom-modal-back: #2a182c; --background--buttons-hover: #8a5a92; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #47c7fc 100%); --session-results-hover: #3b2440; --table-dnf: #8a7b8f; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #d8c6d8; --text-tertiary: #b59eb5; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } body.mclaren-theme { --background: #120b08; --generals: #1a100c; --superficials: #22160f; --dropdown-back: #3b2a1f; --elements: #2a1b13; --augment-buttons-bg: #4a3124; --augment-buttons-hover: #5a3a2b; --dropdown-hover: #4b3326; --table-default: #19100c; --table-default-odd: #1f140f; --table-header: #342218; --dark-text: #b7ada8; --disabled-text: #7f746e; --modal-text: #8f847d; --negative-text: #292929; --new-primary: #ff8000; --new-primary-dark: #d86a00; --new-secondary: #47c7fc; --new-secondary-dark: #2da6d6; --text-selected: #ffd9b5; --text-selected-secondary: #cdefff; --table-hover: #5a3a2b; --elements-hover: #3b281e; --box-shadow: #0000003a; --separator: #5a4033; --separator-light: #6a4b3c; --background-general: #140d0a; --tools-general: #18100c; --component-general: #5b3a2c; --component-general-hover: #6a4534; --component-general-hover-extra: #7a5140; --overall-holder: #281a13; --dropdown-back-general: #b77c5d; --active-general: #1f120d; --row-cell: #160d09; --row-odd-cell: #1b110c; --special-columns: #5f3f33; --h2h-back: #22160f; --h2h-back-hover: #2f1f16; --active-buttons-back: #7a5140; --prob-viewer-hover: #6a4534; --custom-modal-back: #2a1b13; --background--buttons-hover: #8a5a44; --primary-gradient: linear-gradient(to right, var(--new-primary) 0%, #ffb46b 100%); --session-results-hover: #3b281e; --table-dnf: #7f746e; --new-primary-background-dropdown: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --new-primary-background-dropdown-items: color-mix(in srgb, var(--new-primary) 45%, var(--elements)); --new-primary-transparent: color-mix(in srgb, var(--new-primary) 25%, var(--elements)); --tech-grid-highlight-color: var(--new-primary); --text-secondary: #d2c1b7; --text-tertiary: #b0978a; --switch-border-on: var(--new-primary); --switch-border-off: var(--text-general); } ================================================ FILE: vercel.json ================================================ { "headers": [ { "source": "/assets/images/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ] } ================================================ FILE: webpack.config.js ================================================ const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const webpack = require('webpack'); const packageJson = require('./package.json'); module.exports = { mode: process.env.NODE_ENV || 'development', entry: './src/index.js', // Archivo de entrada principal output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', // Path a tu HTML original filename: 'index.html' // El HTML generado irá a 'dist/index.html' }), new MiniCssExtractPlugin({ filename: 'styles.css', }), new CopyWebpackPlugin({ patterns: [ { from: 'assets/images', // ajusta esta ruta a donde tengas tus imágenes to: 'assets/images' }, { from: 'src/data', to: 'data' } ] }), new webpack.DefinePlugin({ APP_VERSION: JSON.stringify(packageJson.version), BUILD_ID: JSON.stringify( process.env.BUILD_ID || process.env.VERCEL_DEPLOYMENT_ID || 'local' ), 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 'process.env.PATREON_CLIENT_ID': JSON.stringify(process.env.PATREON_CLIENT_ID), 'process.env.PATREON_REDIRECT_URI': JSON.stringify(process.env.PATREON_REDIRECT_URI), }) ], resolve: { extensions: ['.js'], fallback: { // Si tu código usa 'buffer' (p.ej. new Buffer o Buffer.from) buffer: require.resolve('buffer/'), "vm": false, "stream": false, "fs": false, "path": false, "crypto": false, }, }, module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', }, }, { test: /\.css$/i, use: [MiniCssExtractPlugin.loader, 'css-loader'], // Usa el plugin en lugar de 'style-loader' }, { test: /\.(png|jpe?g|gif|svg)$/i, type: 'asset/resource', generator: { filename: 'assets/images/[name][ext]', // Copia las imágenes en dist/assets/images }, }, ], }, };