From b0eebc393f5f3f64120e629d1c83d67bc15fc200 Mon Sep 17 00:00:00 2001 From: Andres Rios Tascon Date: Mon, 29 Jun 2026 16:52:22 -0400 Subject: [PATCH 1/5] First version of web app --- .gitignore | 30 +- LICENSE | 201 ++ README.md | 26 +- eslint.config.js | 22 + index.html | 13 + make_html.sh | 19 - make_lstpage.py | 254 --- package-lock.json | 3134 +++++++++++++++++++++++++++++ package.json | 34 + src/App.css | 506 +++++ src/App.tsx | 122 ++ src/api.ts | 32 + src/components/CmssPlotViewer.tsx | 163 ++ src/components/FlatPdfViewer.tsx | 98 + src/components/PlotDetail.tsx | 68 + src/components/PlotGrid.tsx | 99 + src/components/PlotThumbnail.tsx | 60 + src/components/PlotViewer.tsx | 312 +++ src/components/Sidebar.tsx | 184 ++ src/decompress.ts | 22 + src/index.css | 29 + src/main.tsx | 13 + src/plotLayout.ts | 429 ++++ tsconfig.app.json | 25 + tsconfig.json | 7 + tsconfig.node.json | 24 + vite.config.ts | 8 + 27 files changed, 5657 insertions(+), 277 deletions(-) create mode 100644 LICENSE create mode 100644 eslint.config.js create mode 100644 index.html delete mode 100644 make_html.sh delete mode 100644 make_lstpage.py create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/api.ts create mode 100644 src/components/CmssPlotViewer.tsx create mode 100644 src/components/FlatPdfViewer.tsx create mode 100644 src/components/PlotDetail.tsx create mode 100644 src/components/PlotGrid.tsx create mode 100644 src/components/PlotThumbnail.tsx create mode 100644 src/components/PlotViewer.tsx create mode 100644 src/components/Sidebar.tsx create mode 100644 src/decompress.ts create mode 100644 src/index.css create mode 100644 src/main.tsx create mode 100644 src/plotLayout.ts create mode 100644 tsconfig.app.json create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index 8d06f28a..6dbbe234 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,28 @@ -summary/ -compare/ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store + +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.claude/ +CLAUDE.md diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index cda456ed..0775162f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,27 @@ # LSTPerformanceWeb -`python make_lstpage.py` +A static React Single Page Application for browsing LST performance plots generated by CI. Plots are stored as tarballs in archive repositories and decompressed client-side — no backend required. -`sh make_html.sh` +## Development + +```bash +npm install +npm run dev +``` + +## Build + +```bash +npm run build +``` + +The output in `dist/` can be served from any static file host. + +## How it works + +The sidebar fetches directory listings from two GitHub archive repositories: + +- **lst-performance-plots-archive-2026** (`main` branch) +- **TrackLooper-plots-archive** (`cmssw` branch) + +Selecting a run downloads and decompresses its tarball in the browser. Plots are displayed as PDFs in an organized grid with category/metric navigation. The current view is encoded in the URL for deep linking. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 00000000..ef614d25 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + globals: globals.browser, + }, + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 00000000..d63300d1 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + LST Performance Plots + + +
+ + + diff --git a/make_html.sh b/make_html.sh deleted file mode 100644 index 373f295d..00000000 --- a/make_html.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -cd summary/ -rm -f .jobs.txt -for File in $(ls *.md); do - echo "/home/users/phchang/local/bin/pandoc -f markdown -t html -o ${File/.md/.html} ${File}" >> .jobs.txt -done -xargs.sh .jobs.txt -rm -f .jobs.txt -cd ../ - -cd compare/ -rm -f .jobs.txt -for File in $(ls *.md); do - echo "/home/users/phchang/local/bin/pandoc -f markdown -t html -o ${File/.md/.html} ${File}" >> .jobs.txt -done -xargs.sh .jobs.txt -rm -f .jobs.txt -cd ../ diff --git a/make_lstpage.py b/make_lstpage.py deleted file mode 100644 index 21421191..00000000 --- a/make_lstpage.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/bin/env python - -import os -import glob -import sys - -#___________________________________________________________________________________________________ -def write_pages_v2(directory, objecttypes): - - os.system("mkdir -p {directory}".format(directory=directory)) - - ############################################################################################################################## - - # eff - pdgids = [0, 11, 13, 211, 321] - sels = ["base", "loweta", "xtr", "vtr"] - variables = [ - "pt", - "ptzoom", - "ptlow", - "ptlowzoom", - "ptmtv", - "ptmtvzoom", - "eta", - "etazoom", - "etacoarse", - "etacoarsezoom", - "phi", - "phizoom", - "phicoarse", - "phicoarsezoom", - "dxy", - "dxycoarse", - "dxycoarsezoom", - "dz", - "dzcoarse", - "dzcoarsezoom", - "vxy", - "vxycoarse", - "vxycoarsezoom", - ] - breakdowns = ["_breakdown"] - charges = [0, -1, 1] - - plotdir = "../mtv" - plot_width = 250 - plot_large_width = 600 - - index_md = open("{directory}/index.md".format(directory=directory), "w") - index_md.write("# LST Performance\n\n") - - ############################################################################################################################## - - metric = "eff" - for objecttype in objecttypes: - index_md.write("## {}\n\n".format(objecttype)) - index_md.write("### Efficiencies\n\n") - for breakdown in breakdowns: - for selection in sels: - index_md.write("#### For {selectionstr}\n\n".format(selectionstr=get_selectionstr(selection))) - for charge in charges: - summary_file_name = "{objecttype}_{metric}_{selection}_{charge}".format(objecttype=objecttype,metric=metric, selection=selection, charge=charge) - summary_markdown = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name), "w") - TOC = {} - SectionID = 0 - page_name = "Summary Plots of LST {objecttype} Efficiency for {selectionstr} with Charge={chargestr}".format(objecttype=objecttype, selectionstr=get_selectionstr(selection), chargestr=get_chargestr(charge)) - index_md.write("[Charge={chargestr}]({summary_file_name}.html)\n\n".format(selectionstr=get_selectionstr(selection), chargestr=get_chargestr(charge), summary_file_name=summary_file_name)) - for pdgid in pdgids: - SectionID += 1 - pdgidstr = get_pdgidstr(pdgid) - SectionTitle = "{SectionID} {objecttype} {pdgidstr} {metricstr}".format(SectionID=SectionID, objecttype=objecttype, pdgidstr=pdgidstr, metricstr=get_metricstr(metric)) - summary_markdown.write("\n\n## {SectionTitle}\n\n [[back to top](#top)]\n\n".format(SectionTitle=SectionTitle, SectionID=SectionID)) - TOC["#{SectionID}".format(SectionID=SectionID)] = SectionTitle - for variable in variables: - name = "{objecttype}_{selection}_{pdgid}_{charge}_{metric}_{variable}".format(objecttype=objecttype, selection=selection, pdgid=pdgid, charge=charge, metric=metric, variable=variable) - html = "{name}.html".format(name=name) - md = "{name}.md".format(name=name) - f = open("{directory}/{md}".format(directory=directory, md=md), "w") - f.write("# {objecttype} {metricstr} vs. {variable}\n\n[[back to main](./)]\n\n".format(objecttype=objecttype, metricstr=get_metricstr(metric), variable=variable)) - f.write("\n\n") - f.write("## Ratio\n\n[![Ratio]({plotdir}/var/{name}.png){{ width={plot_large_width}px }}]({plotdir}/var/{name}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width)) - if len(breakdown) != 0: - for i in range(5): - f.write("## Denominator {i}\n\n[![Denominator]({plotdir}/den/{name}_den{i}.png){{ width={plot_large_width}px }}]({plotdir}/den/{name}_den{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - f.write("## Numerator {i}\n\n[![Numerator]({plotdir}/num/{name}_num{i}.png){{ width={plot_large_width}px }}]({plotdir}/num/{name}_num{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - for i in range(4): - f.write("## Double Ratio {i}\n\n[![Double Ratio]({plotdir}/ratio/{name}_ratio{i}.png){{ width={plot_large_width}px }}]({plotdir}/ratio/{name}_ratio{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - else: - f.write("## Numerator\n\n[![Numerator]({plotdir}/num/{name}_num0.png){{ width={plot_large_width}px }}]({plotdir}/num/{name}_num0.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - f.close() - summary_markdown.write("[![]({plotdir}/var/{name}.png){{ width={plot_width}px }}]({html})\n".format(plotdir=plotdir, name=name, plot_width=plot_width, html=html)) - - summary_markdown.close() - - # Reopen and add TOC at the top - tmp = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name)) - lines = tmp.readlines() - tmp.close() - - header_lines = [] - header_lines.append("[[back to main](./)]\n\n") - header_lines.append("# {page_name}\n".format(page_name=page_name)) - header_lines.append("\n") - for key in sorted(TOC.keys()): - header_lines.append("[{SectionTitle}]({key})
".format(SectionTitle=TOC[key], key=key)) - - newlines = header_lines + ["\n\n"] + lines - - summary_markdown = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name), "w") - for line in newlines: - summary_markdown.write(line) - - summary_markdown.close() - - - ############################################################################################################################## - - recometrics = ["fakerate", "duplrate"] - - for metric in recometrics: - - index_md.write("### {metricstr}\n\n".format(metricstr=get_metricstr(metric))) - - variables = [ - "pt", - "ptzoom", - "ptlow", - "ptlowzoom", - "ptmtv", - "ptmtvzoom", - "eta", - "etazoom", - "etacoarse", - "etacoarsezoom", - "phi", - "phizoom", - "phicoarse", - "phicoarsezoom", - ] - - for breakdown in breakdowns: - summary_file_name = "{metric}".format(metric=metric) - summary_markdown = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name), "w") - TOC = {} - SectionID = 0 - page_name = "Summary Plots of LST {metricstr}".format(metricstr=get_metricstr(metric)) - index_md.write("[{metricstr}]({summary_file_name}.html)\n\n".format(metricstr=get_metricstr(metric), summary_file_name=summary_file_name)) - for objecttype in objecttypes: - SectionID += 1 - pdgidstr = get_pdgidstr(pdgid) - SectionTitle = "{SectionID} {objecttype} {metricstr}".format(SectionID=SectionID, objecttype=objecttype, metricstr=get_metricstr(metric)) - summary_markdown.write("\n\n## {SectionTitle}\n\n [[back to top](#top)]\n\n".format(SectionTitle=SectionTitle, SectionID=SectionID)) - TOC["#{SectionID}".format(SectionID=SectionID)] = SectionTitle - for variable in variables: - name = "{objecttype}_{metric}_{variable}".format(objecttype=objecttype, metric=metric, variable=variable) - html = "{name}.html".format(name=name) - md = "{name}.md".format(name=name) - f = open("{directory}/{md}".format(directory=directory, md=md), "w") - f.write("# {objecttype} {metricstr} vs. {variable}\n\n[[back to main](./)]\n\n".format(objecttype=objecttype, metricstr=get_metricstr(metric), variable=variable)) - f.write("\n\n") - f.write("## Ratio\n\n[![Ratio]({plotdir}/var/{name}.png){{ width={plot_large_width}px }}]({plotdir}/var/{name}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width)) - if len(breakdown) != 0: - for i in range(5): - f.write("## Denominator {i}\n\n[![Denominator]({plotdir}/den/{name}_den{i}.png){{ width={plot_large_width}px }}]({plotdir}/den/{name}_den{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - f.write("## Numerator {i}\n\n[![Numerator]({plotdir}/num/{name}_num{i}.png){{ width={plot_large_width}px }}]({plotdir}/num/{name}_num{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - for i in range(4): - f.write("## Double Ratio {i}\n\n[![Double Ratio]({plotdir}/ratio/{name}_ratio{i}.png){{ width={plot_large_width}px }}]({plotdir}/ratio/{name}_ratio{i}.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - else: - f.write("## Numerator\n\n[![Numerator]({plotdir}/num/{name}_num0.png){{ width={plot_large_width}px }}]({plotdir}/num/{name}_num0.pdf)\n\n".format(plotdir=plotdir, name=name, plot_large_width=plot_large_width, i=i)) - f.close() - summary_markdown.write("[![]({plotdir}/var/{name}.png){{ width={plot_width}px }}]({html})\n".format(plotdir=plotdir, name=name, plot_width=plot_width, html=html)) - - summary_markdown.close() - - # Reopen and add TOC at the top - tmp = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name)) - lines = tmp.readlines() - tmp.close() - - header_lines = [] - header_lines.append("[[back to main](./)]\n\n") - header_lines.append("# {page_name}\n".format(page_name=page_name)) - header_lines.append("\n") - for key in sorted(TOC.keys()): - header_lines.append("[{SectionTitle}]({key})
".format(SectionTitle=TOC[key], key=key)) - - newlines = header_lines + ["\n\n"] + lines - - summary_markdown = open("{directory}/{summary_file_name}.md".format(directory=directory, summary_file_name=summary_file_name), "w") - for line in newlines: - summary_markdown.write(line) - - summary_markdown.close() - -#___________________________________________________________________________________________________ -def get_pdgidstr(pdgid): - if pdgid == 0: - pdgidstr = "All" - if pdgid == 11: - pdgidstr = "Electron" - if pdgid == 13: - pdgidstr = "Muon" - if pdgid == 211: - pdgidstr = "Pion" - if pdgid == 321: - pdgidstr = "Kaon" - return pdgidstr - -#___________________________________________________________________________________________________ -def get_chargestr(charge): - if charge == 0: - chargestr = "Both" - if charge == 1: - chargestr = "Positive" - if charge == -1: - chargestr = "Negative" - return chargestr - -#___________________________________________________________________________________________________ -def get_selectionstr(selection): - if selection == "base": - selectionstr = "|eta| < 4.5" - if selection == "loweta": - selectionstr = "|eta| < 2.4" - if selection == "xtr": - selectionstr = "1.1 < |eta| < 2.7" - if selection == "vtr": - selectionstr = "not (1.1 < |eta| < 2.7) and |eta| < 2.4" - return selectionstr - -#___________________________________________________________________________________________________ -def get_metricstr(metric): - if metric == "eff": - metricstr = "Efficiency" - if metric == "fakerate": - metricstr = "Fake Rate" - if metric == "duplrate": - metricstr = "Duplicate Rate" - return metricstr - -#___________________________________________________________________________________________________ -def write_footnote(ff): - ff.write(""" -``` {=html} - -``` -""") - -if __name__ == "__main__": - - write_pages_v2("summary", ["TC", "pT5_lower", "pT3_lower", "T5_lower"]) - write_pages_v2("compare", ["TC", "pT5", "pT3", "T5", "pLS", "pT5_lower", "pT3_lower", "T5_lower"]) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..36819f50 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3134 @@ +{ + "name": "lst-performance-web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lst-performance-web", + "version": "0.0.0", + "dependencies": { + "fflate": "^0.8.2", + "js-untar": "^2.0.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-pdf": "^10.4.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/js-untar": "^2.0.0", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.2", + "vite": "^8.0.10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/canvas": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.100.tgz", + "integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==", + "license": "MIT", + "optional": true, + "workspaces": [ + "e2e/*" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/canvas-android-arm64": "0.1.100", + "@napi-rs/canvas-darwin-arm64": "0.1.100", + "@napi-rs/canvas-darwin-x64": "0.1.100", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.100", + "@napi-rs/canvas-linux-arm64-musl": "0.1.100", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-gnu": "0.1.100", + "@napi-rs/canvas-linux-x64-musl": "0.1.100", + "@napi-rs/canvas-win32-arm64-msvc": "0.1.100", + "@napi-rs/canvas-win32-x64-msvc": "0.1.100" + } + }, + "node_modules/@napi-rs/canvas-android-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz", + "integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-arm64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz", + "integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-darwin-x64": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz", + "integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz", + "integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz", + "integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-arm64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz", + "integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz", + "integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-gnu": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz", + "integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-linux-x64-musl": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz", + "integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-arm64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz", + "integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/canvas-win32-x64-msvc": { + "version": "0.1.100", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz", + "integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/js-untar": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/js-untar/-/js-untar-2.0.0.tgz", + "integrity": "sha512-rid45mAPcbPWlqLIyuIqZYE1Iyy9b7DigqYJl2uG4XOJIUmD21OMYlRyVJ5FbQjtPCmYpnRCYp6JAHiasnYymg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.381", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz", + "integrity": "sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==", + "dev": true, + "license": "ISC" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", + "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz", + "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": "^9 || ^10" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", + "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", + "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-untar": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/js-untar/-/js-untar-2.0.0.tgz", + "integrity": "sha512-7CsDLrYQMbLxDt2zl9uKaPZSdmJMvGGQ7wo9hoB3J+z/VcO2w63bXFgHVnjF1+S9wD3zAu8FBVj7EYWjTQ3Z7g==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-cancellable-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-2.0.0.tgz", + "integrity": "sha512-3SEQqTpV9oqVsIWqAcmDuaNeo7yBO3tqPtqGRcKkEo0lrzD3wqbKG9mkxO65KoOgXqj+zH2phJ2LiAsdzlogSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-cancellable-promise?sponsor=1" + } + }, + "node_modules/make-event-props": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-2.0.0.tgz", + "integrity": "sha512-G/hncXrl4Qt7mauJEXSg3AcdYzmpkIITTNl5I+rH9sog5Yw0kK6vseJjCaPfOXqOqQuPUP89Rkhfz5kPS8ijtw==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, + "node_modules/merge-refs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-2.0.0.tgz", + "integrity": "sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/merge-refs?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-pdf": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-10.4.1.tgz", + "integrity": "sha512-kS/35staVCBqS29verTQJQZXw7RfsRCPO3fdJoW1KXylcv7A9dw6DZ3vJXC2w+bIBgLw5FN4pOFvKSQtkQhPfA==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "dequal": "^2.0.3", + "make-cancellable-promise": "^2.0.0", + "make-event-props": "^2.0.0", + "merge-refs": "^2.0.0", + "pdfjs-dist": "5.4.296", + "tiny-invariant": "^1.0.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-pdf?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", + "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "~1.1.2", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..36f0fe36 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "lst-performance-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "fflate": "^0.8.2", + "js-untar": "^2.0.0", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-pdf": "^10.4.1" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@types/js-untar": "^2.0.0", + "@types/node": "^24.12.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^10.2.1", + "eslint-plugin-react-hooks": "^7.1.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.5.0", + "typescript": "~6.0.2", + "typescript-eslint": "^8.58.2", + "vite": "^8.0.10" + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 00000000..db04e6dd --- /dev/null +++ b/src/App.css @@ -0,0 +1,506 @@ +:root { + --header-height: 60px; + --primary-color: #24292e; + --accent-color: #0366d6; + --bg-color: #f6f8fa; + --border-color: #e1e4e8; + --text-color: #24292e; +} + +* { + box-sizing: border-box; +} + +body, html, #root { + height: 100%; + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); +} + +.app-container { + display: flex; + flex-direction: column; + height: 100vh; +} + +.app-header { + height: var(--header-height); + background-color: var(--primary-color); + color: white; + display: flex; + align-items: center; + padding: 0 20px; + flex-shrink: 0; +} + +.app-header h1 { + font-size: 1.2rem; + margin: 0; +} + +.app-body { + display: flex; + flex: 1; + overflow: hidden; +} + +/* Sidebar */ +.sidebar { + border-right: 1px solid var(--border-color); + background-color: white; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.resizer-h { + width: 5px; + cursor: col-resize; + background-color: var(--border-color); + transition: background-color 0.2s; + flex-shrink: 0; +} + +.resizer-h:hover, .is-resizing .resizer-h { + background-color: var(--accent-color); +} + +.is-resizing { + cursor: col-resize; + user-select: none; +} + +.sidebar-header { + border-bottom: 1px solid var(--border-color); + background-color: white; + position: sticky; + top: 0; + z-index: 10; +} + +.sidebar h2 { + font-size: 1rem; + padding: 15px 15px 10px 15px; + margin: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.search-container { + padding: 0 15px 15px 15px; +} + +.search-input { + width: 100%; + padding: 6px 10px; + border: 1px solid var(--border-color); + border-radius: 4px; + font-size: 0.85rem; + outline: none; +} + +.search-input:focus { + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.1); +} + +.no-results { + padding: 15px; + color: #6a737d; + font-style: italic; + font-size: 0.85rem; + text-align: center; +} + +.directory-list { + list-style: none; + padding: 0; + margin: 0; + overflow: auto; + flex: 1; +} + +.run-item, .dir-item { + padding: 8px 15px; + cursor: pointer; + font-size: 0.85rem; + white-space: nowrap; + min-width: min-content; + display: flex; + align-items: center; +} + +.run-item-content { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.run-name { flex: 1; } + +.run-item:hover, .dir-item:hover { background-color: #f1f8ff; } + +.run-item.selected { + background-color: #e7f3ff; + border-left: 4px solid var(--accent-color); + font-weight: bold; +} + +.repo-tag { + font-size: 0.65rem; + background-color: #e1e4e8; + color: #586069; + padding: 1px 5px; + border-radius: 8px; + font-weight: normal; + flex-shrink: 0; +} + +.run-item.selected .repo-tag { + background-color: var(--accent-color); + color: white; +} + +.hw-tag { + font-size: 0.65rem; + padding: 1px 5px; + border-radius: 8px; + font-weight: normal; + flex-shrink: 0; +} + +.hw-tag--gpu { + background-color: #d4edda; + color: #155724; +} + +.hw-tag--cpu { + background-color: #fff3cd; + color: #856404; +} + +.run-item.selected .hw-tag--gpu { + background-color: #28a745; + color: white; +} + +.run-item.selected .hw-tag--cpu { + background-color: #fd7e14; + color: white; +} + +.tree-list { + list-style: none; + padding: 0; + margin: 0; +} + +.icon { + margin-right: 8px; + font-size: 1rem; + width: 1.2rem; + display: inline-block; + text-align: center; +} + +.dir-item { + font-weight: 600; + color: #444; +} + +/* Content area */ +.content-area { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.error-message { + background-color: #ffeef0; + color: #d73a49; + padding: 10px 20px; + border-bottom: 1px solid #d73a49; + flex-shrink: 0; +} + +.loading { + padding: 20px; + text-align: center; + color: #6a737d; +} + +/* Plot Viewer */ +.plot-viewer { + display: flex; + flex-direction: column; + height: 100%; + overflow: hidden; +} + +.viewer-placeholder { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + color: #6a737d; + font-style: italic; + font-size: 1rem; +} + +/* Navigation bar */ +.nav-bar { + background-color: white; + border-bottom: 1px solid var(--border-color); + padding: 8px 16px; + display: flex; + flex-wrap: wrap; + gap: 16px; + align-items: center; + flex-shrink: 0; +} + +.nav-group { + display: flex; + align-items: center; + gap: 6px; +} + +.nav-label { + font-size: 0.8rem; + font-weight: 600; + color: #586069; + white-space: nowrap; +} + +.nav-tabs { + display: flex; + flex-wrap: wrap; + gap: 2px; +} + +.nav-tab { + padding: 4px 10px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: var(--bg-color); + cursor: pointer; + font-size: 0.8rem; + color: var(--text-color); + white-space: nowrap; + transition: background-color 0.15s, color 0.15s; +} + +.nav-tab:hover { background-color: #e7f3ff; } + +.nav-tab.active { + background-color: var(--accent-color); + color: white; + border-color: var(--accent-color); +} + +.nav-select { + padding: 4px 8px; + border: 1px solid var(--border-color); + border-radius: 4px; + background: white; + font-size: 0.8rem; + color: var(--text-color); + cursor: pointer; + outline: none; +} + +.nav-select:focus { border-color: var(--accent-color); } + +/* Plot grid */ +.plot-grid-container { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.plot-section { + margin-bottom: 24px; +} + +.plot-section-header { + font-size: 1rem; + font-weight: 600; + color: var(--primary-color); + margin: 0 0 10px 0; + padding-bottom: 6px; + border-bottom: 2px solid var(--border-color); +} + +.plot-section-grid { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +/* Individual thumbnail */ +.plot-thumbnail { + display: flex; + flex-direction: column; + align-items: center; + cursor: pointer; + border: 2px solid var(--border-color); + border-radius: 4px; + padding: 4px; + background: white; + transition: border-color 0.15s, box-shadow 0.15s; + width: 210px; +} + +.plot-thumbnail:hover { + border-color: var(--accent-color); + box-shadow: 0 2px 8px rgba(3, 102, 214, 0.15); +} + +.plot-thumbnail.selected { + border-color: var(--accent-color); + box-shadow: 0 0 0 3px rgba(3, 102, 214, 0.2); +} + +.thumbnail-pdf { + width: 200px; + height: 150px; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background-color: #f8f8f8; +} + +.thumbnail-pdf .react-pdf__Page canvas { + max-width: 200px !important; + height: auto !important; +} + +.thumbnail-placeholder { + width: 200px; + height: 150px; + background: linear-gradient(135deg, #f0f0f0 25%, #e8e8e8 50%, #f0f0f0 75%); + background-size: 400% 400%; + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.thumbnail-label { + margin-top: 4px; + font-size: 0.72rem; + color: #444; + text-align: center; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* Plot detail */ +.plot-detail-container { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.plot-detail-back { + display: inline-flex; + align-items: center; + gap: 6px; + margin-bottom: 16px; + cursor: pointer; + color: var(--accent-color); + font-size: 0.9rem; + font-weight: 600; + background: none; + border: none; + padding: 0; +} + +.plot-detail-back:hover { text-decoration: underline; } + +.plot-detail-title { + font-size: 1.1rem; + font-weight: 600; + margin: 0 0 16px 0; + color: var(--primary-color); +} + +.plot-detail-section { + margin-bottom: 20px; +} + +.plot-detail-section-label { + font-size: 0.85rem; + font-weight: 600; + color: #586069; + margin: 0 0 8px 0; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.plot-detail-pdf { + border: 1px solid var(--border-color); + border-radius: 4px; + overflow: hidden; + display: inline-block; + background: white; +} + +.plot-detail-pdf .react-pdf__Page { + margin: 0; +} + +.react-pdf__Page { + margin-bottom: 0; +} + +/* Non-MTV fallback */ +.flat-file-list { + flex: 1; + overflow-y: auto; + padding: 16px; +} + +.flat-file-group { + margin-bottom: 16px; +} + +.flat-file-group-name { + font-size: 0.9rem; + font-weight: 600; + color: var(--primary-color); + margin: 0 0 8px 0; + padding-bottom: 4px; + border-bottom: 1px solid var(--border-color); +} + +.flat-file-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 0; + cursor: pointer; + font-size: 0.85rem; + color: var(--accent-color); + border-bottom: 1px solid #f0f0f0; +} + +.flat-file-item:hover { text-decoration: underline; } + +.flat-pdf-display { + margin-top: 12px; + border: 1px solid var(--border-color); + border-radius: 4px; + overflow: hidden; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 00000000..81bcf862 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react'; +import Sidebar from './components/Sidebar'; +import PlotViewer from './components/PlotViewer'; +import { fetchDirectories, fetchTarball } from './api'; +import type { GitHubContent } from './api'; +import { decompressTarGz, revokeFileUrls } from './decompress'; +import type { DecompressedFile } from './decompress'; +import './App.css'; + +const REPOS = [ + { name: 'lst-performance-plots-archive-2026', branch: 'main' }, + { name: 'TrackLooper-plots-archive', branch: 'cmssw' }, +]; + +function App() { + const [directories, setDirectories] = useState([]); + const [selectedDir, setSelectedDir] = useState(null); + const [plotFiles, setPlotFiles] = useState([]); + const [isLoadingDirs, setIsLoadingDirs] = useState(true); + const [isExtracting, setIsExtracting] = useState(false); + const [error, setError] = useState(null); + const [sidebarWidth, setSidebarWidth] = useState(300); + const [isResizing, setIsResizing] = useState(false); + + useEffect(() => { + async function initApp() { + try { + const allDirsResults = await Promise.all(REPOS.map(repo => fetchDirectories(repo.name))); + const allDirs = allDirsResults.flat(); + setDirectories(allDirs); + + const params = new URLSearchParams(window.location.search); + const runName = params.get('run'); + if (runName) { + const matchedDir = allDirs.find(d => d.name === runName); + if (matchedDir) handleDirSelect(matchedDir); + } + } catch (err) { + setError('Failed to load directories from GitHub.'); + console.error(err); + } finally { + setIsLoadingDirs(false); + } + } + initApp(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + setIsResizing(true); + }; + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isResizing) return; + setSidebarWidth(Math.max(150, Math.min(600, e.clientX))); + }; + const handleMouseUp = () => setIsResizing(false); + + if (isResizing) { + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + } + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }, [isResizing]); + + const handleDirSelect = async (dir: GitHubContent) => { + setSelectedDir(prev => { + if (prev?.path === dir.path && prev?.repo === dir.repo) return prev; + return dir; + }); + + const url = new URL(window.location.href); + url.searchParams.set('run', dir.name); + window.history.pushState({}, '', url); + + setPlotFiles(prev => { revokeFileUrls(prev); return []; }); + setIsExtracting(true); + setError(null); + + try { + const repoInfo = REPOS.find(r => r.name === dir.repo); + const buffer = await fetchTarball(dir.repo, dir.path, repoInfo?.branch ?? 'main'); + const files = await decompressTarGz(buffer); + setPlotFiles(files); + } catch (err) { + setError(`Failed to process plots for ${dir.name}.`); + console.error(err); + } finally { + setIsExtracting(false); + } + }; + + return ( +
+
+

LST Performance Plots

+
+
+
+ +
+
+
+ {error &&
{error}
} + +
+
+
+ ); +} + +export default App; diff --git a/src/api.ts b/src/api.ts new file mode 100644 index 00000000..6887befa --- /dev/null +++ b/src/api.ts @@ -0,0 +1,32 @@ +const REPO_OWNER = 'SegmentLinking'; +const BASE_API_URL = `https://api.github.com/repos/${REPO_OWNER}`; +const RAW_URL = `https://raw.githubusercontent.com/${REPO_OWNER}`; + +export interface GitHubContent { + name: string; + path: string; + type: 'dir' | 'file'; + download_url: string | null; + repo: string; +} + +export async function fetchDirectories(repoName: string): Promise { + const response = await fetch(`${BASE_API_URL}/${repoName}/contents`); + if (!response.ok) { + throw new Error(`Failed to fetch directories for ${repoName}: ${response.statusText}`); + } + const data: GitHubContent[] = await response.json(); + return data + .filter(item => item.type === 'dir') + .map(item => ({ ...item, repo: repoName })) + .reverse(); +} + +export async function fetchTarball(repoName: string, dirPath: string, branch: string = 'main'): Promise { + const url = `${RAW_URL}/${repoName}/${branch}/${dirPath}/plots.tar.gz`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch tarball from ${repoName} (${branch}): ${response.statusText}`); + } + return await response.arrayBuffer(); +} diff --git a/src/components/CmssPlotViewer.tsx b/src/components/CmssPlotViewer.tsx new file mode 100644 index 00000000..3901e0d0 --- /dev/null +++ b/src/components/CmssPlotViewer.tsx @@ -0,0 +1,163 @@ +import React, { useMemo, useState, useEffect } from 'react'; +import { Document, Page } from 'react-pdf'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; +import 'react-pdf/dist/Page/TextLayer.css'; +import type { DecompressedFile } from '../decompress'; +import type { CmssPlotFile } from '../plotLayout'; +import { + parseCmssFiles, + formatCategoryName, + formatCmssStemLabel, + CMSS_GROUP_LABELS, +} from '../plotLayout'; +import PlotThumbnail from './PlotThumbnail'; +import FlatPdfViewer from './FlatPdfViewer'; + +interface CmssPlotViewerProps { + files: DecompressedFile[]; +} + +const CmssPlotViewer: React.FC = ({ files }) => { + const parsed = useMemo(() => parseCmssFiles(files), [files]); + + const [category, setCategory] = useState(''); + const [group, setGroup] = useState(''); + const [detail, setDetail] = useState(null); + + // Initialise from URL params when parsed data changes, falling back to defaults + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const first = parsed.categories[0] ?? ''; + const urlCat = params.get('cat') ?? ''; + const resolvedCat = parsed.categories.includes(urlCat) ? urlCat : first; + setCategory(resolvedCat); + + const groups = parsed.groupsForCategory.get(resolvedCat) ?? []; + const urlGrp = params.get('grp') ?? ''; + const resolvedGrp = groups.includes(urlGrp) ? urlGrp : (groups[0] ?? ''); + setGroup(resolvedGrp); + + const urlPlot = params.get('plot') ?? ''; + if (urlPlot) { + const key = `${resolvedCat}\x00${resolvedGrp}`; + const catFiles = parsed.filesForCategoryGroup.get(key) ?? []; + setDetail(catFiles.find(f => f.stem === urlPlot) ?? null); + } else { + setDetail(null); + } + }, [parsed]); + + // Sync nav state → URL + useEffect(() => { + if (!category) return; + const url = new URL(window.location.href); + url.searchParams.set('cat', category); + url.searchParams.set('grp', group); + detail + ? url.searchParams.set('plot', detail.stem) + : url.searchParams.delete('plot'); + url.searchParams.delete('ds'); + url.searchParams.delete('obj'); + url.searchParams.delete('metric'); + url.searchParams.delete('sel'); + url.searchParams.delete('charge'); + window.history.replaceState({}, '', url); + }, [category, group, detail]); + + if (parsed.categories.length === 0) { + return ; + } + + const groups = parsed.groupsForCategory.get(category) ?? []; + const currentFiles = parsed.filesForCategoryGroup.get(`${category}\x00${group}`) ?? []; + + const handleCategoryChange = (cat: string) => { + setCategory(cat); + setGroup(parsed.groupsForCategory.get(cat)?.[0] ?? ''); + setDetail(null); + }; + + if (detail) { + return ( +
+ +

{detail.stem}

+
+
+ Loading…
} + > + + +
+
+ + ); + } + + return ( +
+
+ {parsed.categories.length > 0 && ( +
+ Category: +
+ {parsed.categories.map(cat => ( + + ))} +
+
+ )} + + {groups.length > 0 && ( +
+ Group: +
+ {groups.map(g => ( + + ))} +
+
+ )} +
+ +
+
+
+ {currentFiles.map(f => ( + setDetail(f)} + /> + ))} +
+
+
+
+ ); +}; + +export default CmssPlotViewer; diff --git a/src/components/FlatPdfViewer.tsx b/src/components/FlatPdfViewer.tsx new file mode 100644 index 00000000..70ffbe05 --- /dev/null +++ b/src/components/FlatPdfViewer.tsx @@ -0,0 +1,98 @@ +import React, { useMemo, useState, useEffect, useRef } from 'react'; +import { Document, Page } from 'react-pdf'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; +import 'react-pdf/dist/Page/TextLayer.css'; +import type { DecompressedFile } from '../decompress'; + +interface FlatPdfViewerProps { + files: DecompressedFile[]; +} + +const FlatPdfViewer: React.FC = ({ files }) => { + const [selected, setSelected] = useState(null); + const [listWidth, setListWidth] = useState(280); + const [isResizing, setIsResizing] = useState(false); + const containerRef = useRef(null); + + const handleMouseDown = (e: React.MouseEvent) => { + e.preventDefault(); + setIsResizing(true); + }; + + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!isResizing || !containerRef.current) return; + const left = containerRef.current.getBoundingClientRect().left; + setListWidth(Math.max(150, Math.min(600, e.clientX - left))); + }; + const handleMouseUp = () => setIsResizing(false); + if (isResizing) { + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('mouseup', handleMouseUp); + } + return () => { + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('mouseup', handleMouseUp); + }; + }, [isResizing]); + + const groups = useMemo(() => { + const map = new Map(); + for (const f of files) { + const parts = f.name.split('/'); + const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '/'; + if (!map.has(dir)) map.set(dir, []); + map.get(dir)!.push(f); + } + return map; + }, [files]); + + return ( +
+
+
+ {[...groups.entries()].map(([dir, groupFiles]) => ( +
+

{dir}

+ {groupFiles.map(f => { + const name = f.name.split('/').at(-1) ?? f.name; + return ( +
setSelected(f)} + > + {name} +
+ ); + })} +
+ ))} +
+
+
+ +
+ {selected ? ( + Loading…
} + > + + + ) : ( +
+ Select a plot from the list. +
+ )} +
+
+ ); +}; + +export default FlatPdfViewer; diff --git a/src/components/PlotDetail.tsx b/src/components/PlotDetail.tsx new file mode 100644 index 00000000..1b2ac09d --- /dev/null +++ b/src/components/PlotDetail.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { Document, Page } from 'react-pdf'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; +import 'react-pdf/dist/Page/TextLayer.css'; +import type { PlotInfo, ParsedDataset } from '../plotLayout'; +import { formatMetric, formatVariable, PDGID_LABELS, SELECTION_LABELS, CHARGE_LABELS } from '../plotLayout'; + +interface PlotDetailProps { + mainPlot: PlotInfo; + dataset: ParsedDataset; + onBack: () => void; +} + +function sectionLabel(breakdown: PlotInfo): string { + if (breakdown.breakdownType === 'den') return `Denominator ${breakdown.breakdownIndex}`; + if (breakdown.breakdownType === 'num') return `Numerator ${breakdown.breakdownIndex}`; + if (breakdown.breakdownType === 'ratio') return `Double Ratio ${breakdown.breakdownIndex}`; + return 'Breakdown'; +} + +function makePlotTitle(plot: PlotInfo): string { + const metric = formatMetric(plot.metric); + const variable = formatVariable(plot.variable); + if (plot.metric === 'eff') { + const pdgLabel = plot.pdgid !== undefined ? (PDGID_LABELS[plot.pdgid] ?? String(plot.pdgid)) : ''; + const selLabel = plot.selection ? (SELECTION_LABELS[plot.selection] ?? plot.selection) : ''; + const chLabel = plot.charge !== undefined ? (CHARGE_LABELS[plot.charge] ?? String(plot.charge)) : ''; + return `${plot.objectType} ${metric} — ${variable} | ${pdgLabel}, ${selLabel}, charge: ${chLabel}`; + } + return `${plot.objectType} ${metric} — ${variable}`; +} + +const SinglePdf: React.FC<{ url: string; label: string }> = ({ url, label }) => ( +
+

{label}

+
+ Loading…
}> + + +
+ +); + +const PlotDetail: React.FC = ({ mainPlot, dataset, onBack }) => { + const breakdownList = dataset.breakdowns.get(mainPlot.stem) ?? []; + + return ( +
+ +

{makePlotTitle(mainPlot)}

+ + + + {breakdownList.map(bd => ( + + ))} +
+ ); +}; + +export default PlotDetail; diff --git a/src/components/PlotGrid.tsx b/src/components/PlotGrid.tsx new file mode 100644 index 00000000..0e4d3964 --- /dev/null +++ b/src/components/PlotGrid.tsx @@ -0,0 +1,99 @@ +import React from 'react'; +import PlotThumbnail from './PlotThumbnail'; +import type { ParsedDataset, PlotInfo } from '../plotLayout'; +import { + formatVariable, + PDGID_LABELS, + makeNavKey, + getEffStem, + getRecoStem, +} from '../plotLayout'; + +interface PlotGridProps { + dataset: ParsedDataset; + objectType: string; + metric: string; + selection: string; + charge: number; + selectedStem: string | null; + onSelect: (plot: PlotInfo) => void; +} + +const PlotGrid: React.FC = ({ + dataset, + objectType, + metric, + selection, + charge, + selectedStem, + onSelect, +}) => { + if (metric === 'eff') { + const pdgidKey = makeNavKey(objectType, metric, selection, charge); + const pdgids = dataset.pdgidsForObjMetricSelCharge.get(pdgidKey) ?? []; + + const varKey = (pdgid: number) => makeNavKey(objectType, metric, selection, charge, pdgid); + + return ( +
+ {pdgids.map(pdgid => { + const variables = dataset.variablesForNav.get(varKey(pdgid)) ?? []; + return ( +
+ {pdgids.length > 1 && ( +

+ {PDGID_LABELS[pdgid] ?? String(pdgid)} +

+ )} +
+ {variables.map(variable => { + const stem = getEffStem(objectType, selection, pdgid, charge, variable); + const plot = dataset.plotsByKey.get(stem); + if (!plot) return null; + return ( + onSelect(plot)} + /> + ); + })} +
+
+ ); + })} +
+ ); + } + + // Reco metrics: flat grid of variables + const vKey = makeNavKey(objectType, metric); + const variables = dataset.variablesForNav.get(vKey) ?? []; + + return ( +
+
+
+ {variables.map(variable => { + const stem = getRecoStem(objectType, metric, variable); + const plot = dataset.plotsByKey.get(stem); + if (!plot) return null; + return ( + onSelect(plot)} + /> + ); + })} +
+
+
+ ); +}; + +export default PlotGrid; diff --git a/src/components/PlotThumbnail.tsx b/src/components/PlotThumbnail.tsx new file mode 100644 index 00000000..82655e08 --- /dev/null +++ b/src/components/PlotThumbnail.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { Document, Page } from 'react-pdf'; +import 'react-pdf/dist/Page/AnnotationLayer.css'; +import 'react-pdf/dist/Page/TextLayer.css'; + +interface PlotThumbnailProps { + url: string; + label: string; + isSelected: boolean; + onClick: () => void; +} + +const PlotThumbnail: React.FC = ({ url, label, isSelected, onClick }) => { + const [shouldRender, setShouldRender] = useState(false); + const containerRef = useRef(null); + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setShouldRender(true); + observer.disconnect(); + } + }, + { threshold: 0, rootMargin: '300px' }, + ); + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return ( +
+
+ {shouldRender ? ( + + + + ) : ( +
+ )} +
+
{label}
+
+ ); +}; + +export default PlotThumbnail; diff --git a/src/components/PlotViewer.tsx b/src/components/PlotViewer.tsx new file mode 100644 index 00000000..989bdb82 --- /dev/null +++ b/src/components/PlotViewer.tsx @@ -0,0 +1,312 @@ +import React, { useMemo, useState, useEffect } from 'react'; +import type { DecompressedFile } from '../decompress'; +import type { PlotInfo } from '../plotLayout'; +import { + parseFiles, + formatMetric, + formatDatasetName, + SELECTION_LABELS, + CHARGE_LABELS, + makeNavKey, +} from '../plotLayout'; +import PlotGrid from './PlotGrid'; +import PlotDetail from './PlotDetail'; +import CmssPlotViewer from './CmssPlotViewer'; +import FlatPdfViewer from './FlatPdfViewer'; + +interface PlotViewerProps { + files: DecompressedFile[]; + isExtracting: boolean; +} + +const PlotViewer: React.FC = ({ files, isExtracting }) => { + const parsedFiles = useMemo(() => parseFiles(files), [files]); + const isMtvRun = parsedFiles.isMtvRun; + + const [datasetName, setDatasetName] = useState(''); + const [objectType, setObjectType] = useState(''); + const [metric, setMetric] = useState(''); + const [selection, setSelection] = useState(''); + const [charge, setCharge] = useState(0); + const [detailPlot, setDetailPlot] = useState(null); + + // Initialise nav state from URL params when files change, falling back to defaults + useEffect(() => { + if (!isMtvRun || parsedFiles.datasets.size === 0) { + setDatasetName(''); + setObjectType(''); + setMetric(''); + setSelection(''); + setCharge(0); + setDetailPlot(null); + return; + } + + const params = new URLSearchParams(window.location.search); + const firstDs = parsedFiles.datasets.keys().next().value as string; + const urlDs = params.get('ds') ?? ''; + const resolvedDs = parsedFiles.datasets.has(urlDs) ? urlDs : firstDs; + setDatasetName(resolvedDs); + + const ds = parsedFiles.datasets.get(resolvedDs)!; + + const firstObj = ds.objectTypes[0] ?? ''; + const urlObj = params.get('obj') ?? ''; + const resolvedObj = ds.objectTypes.includes(urlObj) ? urlObj : firstObj; + setObjectType(resolvedObj); + + const metrics = ds.metricsForObj.get(resolvedObj) ?? []; + const firstMetric = metrics[0] ?? ''; + const urlMetric = params.get('metric') ?? ''; + const resolvedMetric = metrics.includes(urlMetric) ? urlMetric : firstMetric; + setMetric(resolvedMetric); + + if (resolvedMetric === 'eff') { + const sels = ds.selectionsForObjMetric.get(makeNavKey(resolvedObj, 'eff')) ?? []; + const firstSel = sels[0] ?? ''; + const urlSel = params.get('sel') ?? ''; + const resolvedSel = sels.includes(urlSel) ? urlSel : firstSel; + setSelection(resolvedSel); + + const chargeOpts = ds.chargesForObjMetricSel.get(makeNavKey(resolvedObj, 'eff', resolvedSel)) ?? []; + const firstCharge = chargeOpts[0] ?? 0; + const urlCharge = parseInt(params.get('charge') ?? ''); + setCharge(chargeOpts.includes(urlCharge) ? urlCharge : firstCharge); + } else { + setSelection(''); + setCharge(0); + } + + const urlPlot = params.get('plot') ?? ''; + setDetailPlot(urlPlot ? (ds.plotsByKey.get(urlPlot) ?? null) : null); + }, [parsedFiles]); // eslint-disable-line react-hooks/exhaustive-deps + + // Sync nav state → URL + useEffect(() => { + if (!isMtvRun || !datasetName) return; + const url = new URL(window.location.href); + url.searchParams.set('ds', datasetName); + url.searchParams.set('obj', objectType); + url.searchParams.set('metric', metric); + if (metric === 'eff' && selection) { + url.searchParams.set('sel', selection); + url.searchParams.set('charge', String(charge)); + } else { + url.searchParams.delete('sel'); + url.searchParams.delete('charge'); + } + detailPlot + ? url.searchParams.set('plot', detailPlot.stem) + : url.searchParams.delete('plot'); + url.searchParams.delete('cat'); + url.searchParams.delete('grp'); + window.history.replaceState({}, '', url); + }, [isMtvRun, datasetName, objectType, metric, selection, charge, detailPlot]); // eslint-disable-line react-hooks/exhaustive-deps + + function resetNavForDataset(dsName: string) { + const ds = parsedFiles.datasets.get(dsName); + if (!ds) return; + + const firstObj = ds.objectTypes[0] ?? ''; + setObjectType(firstObj); + + const metrics = ds.metricsForObj.get(firstObj) ?? []; + const firstMetric = metrics[0] ?? ''; + setMetric(firstMetric); + + if (firstMetric === 'eff') { + const sels = ds.selectionsForObjMetric.get(makeNavKey(firstObj, 'eff')) ?? []; + const firstSel = sels[0] ?? ''; + setSelection(firstSel); + const charges = ds.chargesForObjMetricSel.get(makeNavKey(firstObj, 'eff', firstSel)) ?? []; + setCharge(charges[0] ?? 0); + } else { + setSelection(''); + setCharge(0); + } + } + + const handleDatasetChange = (name: string) => { + setDatasetName(name); + resetNavForDataset(name); + setDetailPlot(null); + }; + + const handleObjectTypeChange = (obj: string) => { + const ds = parsedFiles.datasets.get(datasetName); + if (!ds) return; + setObjectType(obj); + const metrics = ds.metricsForObj.get(obj) ?? []; + const firstMetric = metrics[0] ?? ''; + setMetric(firstMetric); + if (firstMetric === 'eff') { + const sels = ds.selectionsForObjMetric.get(makeNavKey(obj, 'eff')) ?? []; + const firstSel = sels[0] ?? ''; + setSelection(firstSel); + const charges = ds.chargesForObjMetricSel.get(makeNavKey(obj, 'eff', firstSel)) ?? []; + setCharge(charges[0] ?? 0); + } + setDetailPlot(null); + }; + + const handleMetricChange = (m: string) => { + const ds = parsedFiles.datasets.get(datasetName); + if (!ds) return; + setMetric(m); + if (m === 'eff') { + const sels = ds.selectionsForObjMetric.get(makeNavKey(objectType, 'eff')) ?? []; + const firstSel = sels[0] ?? ''; + setSelection(firstSel); + const charges = ds.chargesForObjMetricSel.get(makeNavKey(objectType, 'eff', firstSel)) ?? []; + setCharge(charges[0] ?? 0); + } + setDetailPlot(null); + }; + + const handleSelectionChange = (sel: string) => { + const ds = parsedFiles.datasets.get(datasetName); + if (!ds) return; + setSelection(sel); + const charges = ds.chargesForObjMetricSel.get(makeNavKey(objectType, metric, sel)) ?? []; + setCharge(charges[0] ?? 0); + setDetailPlot(null); + }; + + if (isExtracting) { + return
Decompressing plots…
; + } + + if (files.length === 0) { + return
Select a CI run from the sidebar to view plots.
; + } + + if (!parsedFiles.isMtvRun) { + return ; + } + + const datasetNames = [...parsedFiles.datasets.keys()]; + const currentDataset = parsedFiles.datasets.get(datasetName); + if (!currentDataset) return ; + + const objectTypes = currentDataset.objectTypes; + const metrics = currentDataset.metricsForObj.get(objectType) ?? []; + const selections = metric === 'eff' + ? (currentDataset.selectionsForObjMetric.get(makeNavKey(objectType, metric)) ?? []) + : []; + const charges = metric === 'eff' && selection + ? (currentDataset.chargesForObjMetricSel.get(makeNavKey(objectType, metric, selection)) ?? []) + : []; + + if (detailPlot) { + return ( + setDetailPlot(null)} + /> + ); + } + + return ( +
+
+ {datasetNames.length > 1 && ( +
+ Dataset: +
+ {datasetNames.map(name => ( + + ))} +
+
+ )} + + {objectTypes.length > 1 && ( +
+ Object: +
+ {objectTypes.map(obj => ( + + ))} +
+
+ )} + + {metrics.length > 0 && ( +
+ Metric: +
+ {metrics.map(m => ( + + ))} +
+
+ )} + + {metric === 'eff' && selections.length > 1 && ( +
+ Selection: + +
+ )} + + {metric === 'eff' && charges.length > 1 && ( +
+ Charge: +
+ {charges.map(ch => ( + + ))} +
+
+ )} +
+ + +
+ ); +}; + +export default PlotViewer; diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 00000000..013b4e93 --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,184 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import type { GitHubContent } from '../api'; + +interface SidebarProps { + directories: GitHubContent[]; + selectedDir: string | null; + onSelect: (dir: GitHubContent) => void; + isLoading: boolean; +} + +interface RunTreeNode { + name: string; + type: 'pr' | 'commit' | 'run'; + children: Record; + dir?: GitHubContent; +} + +function getHardwareTag(dirName: string): 'GPU' | 'CPU' | null { + if (dirName.endsWith('_gpu')) return 'GPU'; + if (dirName.endsWith('_cpu')) return 'CPU'; + return null; +} + +const RunTreeItem: React.FC<{ + node: RunTreeNode; + level: number; + selectedDir: string | null; + onSelect: (dir: GitHubContent) => void; + searchTerm: string; +}> = ({ node, level, selectedDir, onSelect, searchTerm }) => { + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + if (searchTerm) setIsOpen(true); + }, [searchTerm]); + + const hasSelectedChild = useMemo(() => { + const check = (n: RunTreeNode): boolean => { + if (n.dir?.path === selectedDir) return true; + return Object.values(n.children).some(check); + }; + return check(node); + }, [node, selectedDir]); + + useEffect(() => { + if (hasSelectedChild) setIsOpen(true); + }, [hasSelectedChild]); + + if (node.name === 'root') { + const children = Object.values(node.children).sort((a, b) => { + const aNum = parseInt(a.name.replace('PR', '')) || 0; + const bNum = parseInt(b.name.replace('PR', '')) || 0; + return bNum - aNum; + }); + return ( +
    + {children.map(child => ( + + ))} +
+ ); + } + + if (node.type === 'run') { + return ( +
  • onSelect(node.dir!)} + style={{ paddingLeft: `${level * 12 + 15}px` }} + title={`${node.dir?.name} (${node.dir?.repo})`} + > +
    + 🚀 + {node.name.replace(/_(gpu|cpu)$/, '')} + {getHardwareTag(node.dir?.name ?? '') && ( + + {getHardwareTag(node.dir?.name ?? '')} + + )} + {node.dir?.repo.includes('2026') ? '2026' : 'Legacy'} +
    +
  • + ); + } + + const children = Object.values(node.children).sort((a, b) => a.name.localeCompare(b.name)); + if (children.length === 0) return null; + + return ( + <> +
  • setIsOpen(!isOpen)} + style={{ paddingLeft: `${level * 12 + 15}px` }} + > + {isOpen ? '📂' : '📁'} {node.name} +
  • + {isOpen && ( +
      + {children.map(child => ( + + ))} +
    + )} + + ); +}; + +const Sidebar: React.FC = ({ directories, selectedDir, onSelect, isLoading }) => { + const [searchTerm, setSearchTerm] = useState(''); + + const runTree = useMemo(() => { + const root: RunTreeNode = { name: 'root', type: 'pr', children: {} }; + const filtered = directories.filter(dir => + dir.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + filtered.forEach(dir => { + const parts = dir.name.split('_'); + const prName = parts[0] || 'Unknown'; + const commitHash = parts[1] || 'NoHash'; + const rest = parts.slice(2).join('_') || 'default'; + + if (!root.children[prName]) { + root.children[prName] = { name: prName, type: 'pr', children: {} }; + } + const prNode = root.children[prName]; + if (!prNode.children[commitHash]) { + prNode.children[commitHash] = { name: commitHash, type: 'commit', children: {} }; + } + const commitNode = prNode.children[commitHash]; + commitNode.children[rest] = { name: rest, type: 'run', children: {}, dir }; + }); + return root; + }, [directories, searchTerm]); + + return ( + + ); +}; + +export default Sidebar; diff --git a/src/decompress.ts b/src/decompress.ts new file mode 100644 index 00000000..96a1ea69 --- /dev/null +++ b/src/decompress.ts @@ -0,0 +1,22 @@ +import { gunzipSync } from 'fflate'; +import untar from 'js-untar'; + +export interface DecompressedFile { + name: string; + url: string; +} + +export async function decompressTarGz(buffer: ArrayBuffer): Promise { + const decompressed = gunzipSync(new Uint8Array(buffer)); + const files = await untar(decompressed.buffer as ArrayBuffer); + return files + .filter(file => file.name.endsWith('.pdf')) + .map(file => ({ + name: file.name, + url: URL.createObjectURL((file as unknown as { blob: Blob }).blob), + })); +} + +export function revokeFileUrls(files: DecompressedFile[]) { + files.forEach(file => URL.revokeObjectURL(file.url)); +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 00000000..7ddbbf02 --- /dev/null +++ b/src/index.css @@ -0,0 +1,29 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light; + color: #24292e; + background-color: #f6f8fa; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + display: flex; + min-width: 320px; + min-height: 100vh; +} + +#root { + width: 100%; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 00000000..ee68d4f5 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { pdfjs } from 'react-pdf' +import './index.css' +import App from './App.tsx' + +pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs` + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/plotLayout.ts b/src/plotLayout.ts new file mode 100644 index 00000000..b43dc54e --- /dev/null +++ b/src/plotLayout.ts @@ -0,0 +1,429 @@ +export interface PlotInfo { + url: string; + stem: string; // filename without .pdf + objectType: string; + metric: string; + variable: string; + selection?: string; // only for metric='eff' + pdgid?: number; // only for metric='eff' + charge?: number; // only for metric='eff' + breakdownType?: 'den' | 'num' | 'ratio'; + breakdownIndex?: number; +} + +// Per-dataset parsed data (one for each validation_plots_* / comparison_plots_* dir) +export interface ParsedDataset { + plotsByKey: Map; + breakdowns: Map; + objectTypes: string[]; + metricsForObj: Map; + selectionsForObjMetric: Map; + chargesForObjMetricSel: Map; + pdgidsForObjMetricSelCharge: Map; + variablesForNav: Map; +} + +export interface ParsedFiles { + isMtvRun: boolean; + // keyed by directory name, e.g. "validation_plots_8da008-PU200" + datasets: Map; +} + +const SELECTION_SET = new Set(['base', 'loweta', 'xtr', 'vtr']); + +export const METRIC_LABELS: Record = { + eff: 'Efficiency', + fakerate: 'Fake Rate', + duplrate: 'Duplicate Rate', + fakeorduplrate: 'Fake or Dup. Rate', + avgOTlen: 'Avg. OT Length', +}; + +export const SELECTION_LABELS: Record = { + base: '|η| < 4.5', + loweta: '|η| < 2.4', + xtr: '1.1 < |η| < 2.7', + vtr: 'barrel non-transition', +}; + +export const PDGID_LABELS: Record = { + 0: 'All', + 11: 'Electron', + 13: 'Muon', + 211: 'Pion', + 321: 'Kaon', +}; + +export const CHARGE_LABELS: Record = { + 0: 'Both', + 1: 'Positive+', + [-1]: 'Negative−', +}; + +const VARIABLE_ORDER: string[] = [ + 'pt', 'ptzoom', 'ptlow', 'ptlowzoom', 'ptmtv', 'ptmtvzoom', + 'eta', 'etazoom', 'etacoarse', 'etacoarsezoom', + 'phi', 'phizoom', 'phicoarse', 'phicoarsezoom', + 'dxy', 'dxyzoom', 'dxycoarse', 'dxycoarsezoom', + 'dz', 'dzzoom', 'dzcoarse', 'dzcoarsezoom', + 'vxy', 'vxyzoom', 'vxycoarse', 'vxycoarsezoom', +]; + +const VARIABLE_ORDER_IDX = new Map(VARIABLE_ORDER.map((v, i) => [v, i])); + +const VARIABLE_LABELS: Record = { + pt: 'pT', ptzoom: 'pT zoom', ptlow: 'pT low', ptlowzoom: 'pT low zoom', + ptmtv: 'pT (MTV)', ptmtvzoom: 'pT (MTV) zoom', + eta: 'η', etazoom: 'η zoom', etacoarse: 'η coarse', etacoarsezoom: 'η coarse zoom', + phi: 'φ', phizoom: 'φ zoom', phicoarse: 'φ coarse', phicoarsezoom: 'φ coarse zoom', + dxy: 'dxy', dxyzoom: 'dxy zoom', dxycoarse: 'dxy coarse', dxycoarsezoom: 'dxy coarse zoom', + dz: 'dz', dzzoom: 'dz zoom', dzcoarse: 'dz coarse', dzcoarsezoom: 'dz coarse zoom', + vxy: 'vxy', vxyzoom: 'vxy zoom', vxycoarse: 'vxy coarse', vxycoarsezoom: 'vxy coarse zoom', +}; + +export function formatVariable(v: string): string { + return VARIABLE_LABELS[v] ?? v; +} + +export function formatMetric(m: string): string { + return METRIC_LABELS[m] ?? m; +} + +export function formatDatasetName(name: string): string { + if (name.startsWith('validation_plots_')) { + return 'Validation: ' + name.slice('validation_plots_'.length); + } + if (name.startsWith('comparison_plots_')) { + const rest = name.slice('comparison_plots_'.length); + // rest is like "8da008-PU200_8da008-PU200"; the two runs are _ separated + const idx = rest.indexOf('_'); + if (idx !== -1) { + return 'Comparison: ' + rest.slice(0, idx) + ' vs ' + rest.slice(idx + 1); + } + return 'Comparison: ' + rest; + } + return name; +} + +export function sortVariables(vars: string[]): string[] { + return [...vars].sort((a, b) => { + const ia = VARIABLE_ORDER_IDX.get(a) ?? 999; + const ib = VARIABLE_ORDER_IDX.get(b) ?? 999; + if (ia !== ib) return ia - ib; + return a.localeCompare(b); + }); +} + +const SELECTION_ORDER = ['base', 'loweta', 'xtr', 'vtr']; +const PDGID_ORDER = [0, 11, 13, 211, 321]; +const CHARGE_ORDER = [0, 1, -1]; + +function parseStem(stem: string): Omit | null { + const parts = stem.split('_'); + if (parts.length < 2) return null; + const objectType = parts[0]; + + // Efficiency: objectType_selection_pdgid_charge_eff_variable + if (parts.length >= 6 && SELECTION_SET.has(parts[1]) && parts[4] === 'eff') { + const selection = parts[1]; + const pdgid = parseInt(parts[2]); + const charge = parseInt(parts[3]); + const variable = parts.slice(5).join('_'); + if (isNaN(pdgid) || isNaN(charge)) return null; + return { objectType, metric: 'eff', variable, selection, pdgid, charge }; + } + + // Reco metric: objectType_metric_variable + if (parts.length >= 3) { + const metric = parts[1]; + const variable = parts.slice(2).join('_'); + return { objectType, metric, variable }; + } + + return null; +} + +export function makeNavKey(...parts: (string | number)[]): string { + return parts.join('\x00'); +} + +function makeEmptyDataset(): { + plotsByKey: Map; + breakdowns: Map; + objTypesSet: Set; + metricsSet: Map>; + selectionsSet: Map>; + chargesSet: Map>; + pdgidsSet: Map>; + variablesSet: Map>; +} { + return { + plotsByKey: new Map(), + breakdowns: new Map(), + objTypesSet: new Set(), + metricsSet: new Map(), + selectionsSet: new Map(), + chargesSet: new Map(), + pdgidsSet: new Map(), + variablesSet: new Map(), + }; +} + +export function parseFiles(decompressedFiles: { name: string; url: string }[]): ParsedFiles { + // Each entry in rawDatasets corresponds to one *_plots_* directory + const rawDatasets = new Map>(); + + let isMtvRun = false; + + for (const file of decompressedFiles) { + // Capture the immediate parent of mtv/ as the dataset name + const mtvMatch = file.name.match(/\/([^/]+)\/mtv\/(var|den|num|ratio)\/(.+)\.pdf$/); + if (!mtvMatch) continue; + isMtvRun = true; + + const datasetName = mtvMatch[1]; + const category = mtvMatch[2] as 'var' | 'den' | 'num' | 'ratio'; + const filename = mtvMatch[3]; + + if (!rawDatasets.has(datasetName)) rawDatasets.set(datasetName, makeEmptyDataset()); + const ds = rawDatasets.get(datasetName)!; + + // Check for breakdown suffix: _den0, _num0, _ratio0, etc. + const breakdownMatch = filename.match(/^(.+)_(den|num|ratio)(\d+)$/); + if (breakdownMatch) { + const mainStem = breakdownMatch[1]; + const breakdownType = breakdownMatch[2] as 'den' | 'num' | 'ratio'; + const breakdownIndex = parseInt(breakdownMatch[3]); + const parsed = parseStem(mainStem); + if (!parsed) continue; + + const info: PlotInfo = { url: file.url, stem: filename, breakdownType, breakdownIndex, ...parsed }; + if (!ds.breakdowns.has(mainStem)) ds.breakdowns.set(mainStem, []); + ds.breakdowns.get(mainStem)!.push(info); + continue; + } + + if (category !== 'var') continue; + + const parsed = parseStem(filename); + if (!parsed) continue; + + const info: PlotInfo = { url: file.url, stem: filename, ...parsed }; + ds.plotsByKey.set(filename, info); + + const { objectType, metric, selection, pdgid, charge, variable } = parsed; + ds.objTypesSet.add(objectType); + + if (!ds.metricsSet.has(objectType)) ds.metricsSet.set(objectType, new Set()); + ds.metricsSet.get(objectType)!.add(metric); + + if (metric === 'eff' && selection !== undefined && pdgid !== undefined && charge !== undefined) { + const selKey = makeNavKey(objectType, metric); + if (!ds.selectionsSet.has(selKey)) ds.selectionsSet.set(selKey, new Set()); + ds.selectionsSet.get(selKey)!.add(selection); + + const chKey = makeNavKey(objectType, metric, selection); + if (!ds.chargesSet.has(chKey)) ds.chargesSet.set(chKey, new Set()); + ds.chargesSet.get(chKey)!.add(charge); + + const pgKey = makeNavKey(objectType, metric, selection, charge); + if (!ds.pdgidsSet.has(pgKey)) ds.pdgidsSet.set(pgKey, new Set()); + ds.pdgidsSet.get(pgKey)!.add(pdgid); + + const vKey = makeNavKey(objectType, metric, selection, charge, pdgid); + if (!ds.variablesSet.has(vKey)) ds.variablesSet.set(vKey, new Set()); + ds.variablesSet.get(vKey)!.add(variable); + } else if (metric !== 'eff') { + const vKey = makeNavKey(objectType, metric); + if (!ds.variablesSet.has(vKey)) ds.variablesSet.set(vKey, new Set()); + ds.variablesSet.get(vKey)!.add(variable); + } + } + + const BREAKDOWN_ORDER = { den: 0, num: 1, ratio: 2 }; + + // Sort dataset names: validation first, then comparison, then others alphabetically + const datasetOrder = (name: string) => + name.startsWith('validation_plots_') ? 0 : name.startsWith('comparison_plots_') ? 1 : 2; + + const sortedNames = [...rawDatasets.keys()].sort((a, b) => { + const da = datasetOrder(a), db = datasetOrder(b); + if (da !== db) return da - db; + return a.localeCompare(b); + }); + + const datasets = new Map(); + + for (const name of sortedNames) { + const ds = rawDatasets.get(name)!; + + ds.breakdowns.forEach(list => { + list.sort((a, b) => { + const ta = BREAKDOWN_ORDER[a.breakdownType!] ?? 3; + const tb = BREAKDOWN_ORDER[b.breakdownType!] ?? 3; + if (ta !== tb) return ta - tb; + return (a.breakdownIndex ?? 0) - (b.breakdownIndex ?? 0); + }); + }); + + const objectTypes = [...ds.objTypesSet].sort(); + + const metricsForObj = new Map(); + ds.metricsSet.forEach((v, k) => { + const order = ['eff', 'fakerate', 'duplrate', 'fakeorduplrate', 'avgOTlen']; + const ordered = order.filter(m => v.has(m)); + const remaining = [...v].filter(m => !order.includes(m)).sort(); + metricsForObj.set(k, [...ordered, ...remaining]); + }); + + const selectionsForObjMetric = new Map(); + ds.selectionsSet.forEach((v, k) => { + selectionsForObjMetric.set(k, SELECTION_ORDER.filter(s => v.has(s))); + }); + + const chargesForObjMetricSel = new Map(); + ds.chargesSet.forEach((v, k) => { + chargesForObjMetricSel.set(k, CHARGE_ORDER.filter(c => v.has(c))); + }); + + const pdgidsForObjMetricSelCharge = new Map(); + ds.pdgidsSet.forEach((v, k) => { + pdgidsForObjMetricSelCharge.set(k, PDGID_ORDER.filter(p => v.has(p))); + }); + + const variablesForNav = new Map(); + ds.variablesSet.forEach((v, k) => { + variablesForNav.set(k, sortVariables([...v])); + }); + + datasets.set(name, { + plotsByKey: ds.plotsByKey, + breakdowns: ds.breakdowns, + objectTypes, + metricsForObj, + selectionsForObjMetric, + chargesForObjMetricSel, + pdgidsForObjMetricSelCharge, + variablesForNav, + }); + } + + return { isMtvRun, datasets }; +} + +export function getEffStem(objectType: string, selection: string, pdgid: number, charge: number, variable: string): string { + return `${objectType}_${selection}_${pdgid}_${charge}_eff_${variable}`; +} + +export function getRecoStem(objectType: string, metric: string, variable: string): string { + return `${objectType}_${metric}_${variable}`; +} + +// ── CMSSW run parsing ──────────────────────────────────────────────────────── + +export interface CmssPlotFile { + url: string; + stem: string; + category: string; + group: string; +} + +export interface ParsedCmssFiles { + categories: string[]; + groupsForCategory: Map; + filesForCategoryGroup: Map; +} + +// Order matters: longer/more-specific prefixes must come before their prefixes. +const CMSS_GROUP_PREFIXES: [string, string][] = [ + ['effandfake', 'Efficiency & Fake Rate'], + ['dupandfake', 'Duplicate & Fake Rate'], + ['distsim', 'Sim Distributions'], + ['dist', 'Distributions'], + ['resolutions', 'Resolutions'], + ['residual', 'Residuals'], + ['pvassociation', 'PV Association'], + ['hits', 'Hits'], +]; + +export const CMSS_GROUP_LABELS: Record = { + ...Object.fromEntries(CMSS_GROUP_PREFIXES), + other: 'Other', +}; + +const CMSS_GROUP_ORDER = [ + 'effandfake', 'dupandfake', 'dist', 'distsim', + 'resolutions', 'residual', 'pvassociation', 'hits', 'other', +]; + +function detectCmssGroup(stem: string): string { + const lower = stem.toLowerCase(); + for (const [prefix] of CMSS_GROUP_PREFIXES) { + if (lower.startsWith(prefix)) return prefix; + } + return 'other'; +} + +const CATEGORY_LABELS: Record = { + plots_ootb: 'OOTB', + plots_building_highPtTripletStep: 'High-pT Triplet Step', +}; + +export function formatCategoryName(cat: string): string { + if (CATEGORY_LABELS[cat]) return CATEGORY_LABELS[cat]; + const name = cat.replace(/^plots_/, '').replace(/^building_/, ''); + const spaced = name.replace(/([A-Z])/g, ' $1').trim(); + return spaced.charAt(0).toUpperCase() + spaced.slice(1); +} + +export function formatCmssStemLabel(stem: string, group: string): string { + const prefix = group === 'other' ? '' : group; + let label = stem; + if (prefix && stem.toLowerCase().startsWith(prefix.toLowerCase())) { + label = stem.slice(prefix.length); + } + if (!label) return stem; + label = label.charAt(0).toUpperCase() + label.slice(1); + return label.replace(/([A-Z][a-z])/g, ' $1').trim(); +} + +export function parseCmssFiles(decompressedFiles: { name: string; url: string }[]): ParsedCmssFiles { + const categoriesSet = new Set(); + const groupsForCategorySet = new Map>(); + const filesForCategoryGroup = new Map(); + + for (const file of decompressedFiles) { + if (file.name.includes('/mtv/')) continue; + const match = file.name.match(/\/([^/]+)\/([^/]+)\.pdf$/); + if (!match) continue; + + const category = match[1]; + const stem = match[2]; + const group = detectCmssGroup(stem); + + categoriesSet.add(category); + if (!groupsForCategorySet.has(category)) groupsForCategorySet.set(category, new Set()); + groupsForCategorySet.get(category)!.add(group); + + const key = `${category}\x00${group}`; + if (!filesForCategoryGroup.has(key)) filesForCategoryGroup.set(key, []); + filesForCategoryGroup.get(key)!.push({ url: file.url, stem, category, group }); + } + + const sortCat = (cat: string) => { + if (cat === 'plots_ootb') return 0; + if (cat.startsWith('plots_building_')) return 2; + return 1; + }; + const categories = [...categoriesSet].sort((a, b) => { + const d = sortCat(a) - sortCat(b); + return d !== 0 ? d : a.localeCompare(b); + }); + + const groupsForCategory = new Map(); + groupsForCategorySet.forEach((v, k) => { + groupsForCategory.set(k, CMSS_GROUP_ORDER.filter(g => v.has(g))); + }); + + return { categories, groupsForCategory, filesForCategoryGroup }; +} diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 00000000..7f42e5f7 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 00000000..d3c52ea6 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 00000000..d40796b4 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + base: './', +}) From f1e4e0d4d941937a3ddf4b6e4d7905c9290125b9 Mon Sep 17 00:00:00 2001 From: Andres Rios Tascon Date: Mon, 29 Jun 2026 16:55:32 -0400 Subject: [PATCH 2/5] Added CI config --- .github/workflows/deploy.yml | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..11182c3d --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,46 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 From 29d3739a57cf4935418a4e65fb4cef3a00b2db60 Mon Sep 17 00:00:00 2001 From: Andres Rios Tascon Date: Wed, 1 Jul 2026 11:24:38 -0400 Subject: [PATCH 3/5] Added logo and changed title --- public/logo.png | Bin 0 -> 53656 bytes src/App.css | 7 +++++++ src/App.tsx | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 public/logo.png diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a2cf0c45062fc5ba27a131679de22ee01edb8b2c GIT binary patch literal 53656 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%0DYE!Omd~*rr&?j@%5`}O}|+2$B*nykfU{g7}#hc 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 literal 0 HcmV?d00001 diff --git a/src/App.css b/src/App.css index db04e6dd..07f0cc60 100644 --- a/src/App.css +++ b/src/App.css @@ -36,6 +36,13 @@ body, html, #root { flex-shrink: 0; } +.app-logo { + height: 36px; + width: auto; + margin-right: 12px; + flex-shrink: 0; +} + .app-header h1 { font-size: 1.2rem; margin: 0; diff --git a/src/App.tsx b/src/App.tsx index 81bcf862..29719b6a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -98,7 +98,8 @@ function App() { return (
    -

    LST Performance Plots

    + LST logo +

    LST Performance Web

    From 7aefe4cc86b8d166bc7c654311e7ed5c74c69afb Mon Sep 17 00:00:00 2001 From: Andres Rios Tascon Date: Wed, 1 Jul 2026 11:29:26 -0400 Subject: [PATCH 4/5] Updated actions and added dependabot --- .github/dependabot.yml | 10 ++++++++++ .github/workflows/deploy.yml | 8 ++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..79b59ddd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/.github/workflows" + schedule: + interval: "monthly" + groups: + actions: + patterns: + - '*' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 11182c3d..2274700b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,10 +15,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Set up Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: 20 cache: 'npm' @@ -30,7 +30,7 @@ jobs: run: npm run build - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v5 with: path: ./dist @@ -43,4 +43,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@v5 From c6c7e70823460b0e76acdb04ac52436d4c8c3f6a Mon Sep 17 00:00:00 2001 From: Andres Rios Tascon Date: Wed, 1 Jul 2026 12:09:50 -0400 Subject: [PATCH 5/5] Fixed logo path --- src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index 29719b6a..7f29e713 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -98,7 +98,7 @@ function App() { return (
    - LST logo + LST logo

    LST Performance Web