skander101 commited on
Commit
7232589
·
1 Parent(s): ac179d1

feat: replace Jinja2 template with React SPA (Vite + React 18)

Browse files
.gitignore CHANGED
@@ -1,24 +1,8 @@
1
- # Python
2
  __pycache__/
3
- *.py[cod]
4
- *.egg-info/
5
  venv/
6
  .env
7
-
8
- # IDE
9
- .vscode/
10
- .idea/
11
- *.swp
12
- *.swo
13
-
14
- # OS
15
- .DS_Store
16
- Thumbs.db
17
-
18
- # Logs
19
- *.log
20
- /tmp/
21
-
22
- # Project specific
23
- static/
24
- DEPLOY.md
 
 
1
  __pycache__/
2
+ *.pyc
 
3
  venv/
4
  .env
5
+ *.egg-info/
6
+ /dist/
7
+ !frontend/dist/
8
+ node_modules/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ node_modules/
frontend/dist/assets/index-BewHjeaj.js ADDED
The diff for this file is too large to render. See raw diff
 
frontend/dist/assets/index-DI9qLnC9.css ADDED
@@ -0,0 +1 @@
 
 
1
+ *,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{font-family:"IBM Plex Serif",Georgia,Times New Roman,serif;background:#000;color:#a0a0a0;min-height:100vh;overflow-x:hidden}a{color:#d72924;text-decoration:none}a:hover,a:focus,a:active{color:#df5450}h1,h2,h3,h4,h5,h6{font-family:Lora,Cambria,sans-serif;color:#fff}.header{background:linear-gradient(0deg,#000000eb,#d72924eb);padding:20px 32px;position:sticky;top:0;z-index:50}.header-inner{max-width:1400px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px}.header h1{font-size:22px;font-weight:700;font-family:Lora,Cambria,sans-serif;color:#fff}.header span{font-size:13px;color:silver}.status{color:silver;font-size:13px;display:flex;align-items:center;gap:8px}.status-dot{width:8px;height:8px;border-radius:50%;background:#22c55e;display:inline-block;animation:pulse-dot 2s ease-in-out infinite}@keyframes pulse-dot{0%,to{opacity:1;transform:scale(1);box-shadow:0 0 #22c55e99}50%{opacity:.7;transform:scale(.85);box-shadow:0 0 0 6px #22c55e00}}.container{max-width:1400px;margin:0 auto;padding:20px 32px}.tabs{display:flex;gap:4px;margin-bottom:24px;border-bottom:1px solid #333;overflow-x:auto;position:relative}.tab{padding:10px 20px;border:none;background:none;color:#666;font-family:"IBM Plex Serif",Georgia,serif;font-size:14px;font-weight:600;cursor:pointer;white-space:nowrap;position:relative;transition:color .25s ease}.tab:hover{color:silver}.tab.active{color:#d72924}.tab-count{font-size:11px;color:#555;margin-left:4px;transition:color .25s}.tab.active .tab-count{color:#d72924b3}.tab-underline{position:absolute;bottom:-1px;height:2px;background:#d72924;transition:left .3s cubic-bezier(.4,0,.2,1),width .3s cubic-bezier(.4,0,.2,1);box-shadow:0 0 8px #d7292480}.card{background:#0c0c0ceb;border-radius:0;overflow:hidden;border:1px solid #222;display:flex;flex-direction:column;box-shadow:1px 2px 3px #ffffff1a;opacity:0;transform:translateY(15px);transition:transform .2s ease-out,box-shadow .2s ease-out,border-color .2s ease-out,opacity .3s ease-out}.card.revealed{opacity:1;transform:translateY(0)}.card:hover{transform:translateY(-3px) scale(1.02);box-shadow:0 0 20px #d7292426,0 8px 24px #ffffff1a;border-color:#d729244d}.card-image-wrap{overflow:hidden;height:180px}.card-image{width:100%;height:180px;object-fit:cover;background:#111;display:block;transition:transform .3s ease-out}.card:hover .card-image{transform:scale(1.05)}.card-image-placeholder{width:100%;height:180px;background:linear-gradient(135deg,#111,#000);display:flex;align-items:center;justify-content:center;font-size:40px;color:#333}.card-body{padding:14px 18px 18px;flex:1;display:flex;flex-direction:column}.card-rank{font-size:10px;font-weight:700;color:#666;letter-spacing:.05em;margin-bottom:4px}.card-topic{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;color:#d72924;margin-bottom:6px}.card-title{font-size:15px;font-weight:600;font-family:Lora,Cambria,sans-serif;line-height:1.4;margin-bottom:6px;color:#fff;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.card-summary{font-size:13px;color:#a0a0a0;line-height:1.5;margin-bottom:10px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;flex:1}.card-meta{display:flex;align-items:center;gap:10px;font-size:12px;color:silver;margin-bottom:10px;flex-wrap:wrap}.card-meta .source{color:silver}.sponsor{font-size:10px;color:#fbbf24;background:#fbbf241a;padding:2px 6px;border-radius:4px}.bias-badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;letter-spacing:.03em}.bias-left{background:#3b82f626;color:#60a5fa}.bias-left-center{background:#6366f126;color:#818cf8}.bias-center{background:#94a3b826;color:#94a3b8}.bias-right-center{background:#ef444426;color:#f87171}.bias-right{background:#dc262626;color:#ef4444}.bias-satire{background:#a855f726;color:#c084fc}.factuality-badge{font-size:10px;font-weight:600;padding:2px 6px;border-radius:4px;letter-spacing:.03em}.factuality-high{background:#22c55e26;color:#4ade80}.factuality-mixed{background:#fbbf2426;color:#fbbf24}.factuality-low{background:#ef444426;color:#f87171}.factuality-satire{background:#a855f726;color:#c084fc}.category-badge{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.08em;padding:3px 8px}.cat-government{background:#ef444426;color:#f87171}.cat-conglomerate{background:#8b5cf626;color:#a78bfa}.cat-private_equity{background:#fbbf2426;color:#fbbf24}.cat-wealthy_private{background:#ec489926;color:#f472b6}.cat-corporate{background:#3b82f626;color:#60a5fa}.cat-independent{background:#22c55e26;color:#4ade80}.score-bar-wrap{height:4px;background:#222;border-radius:2px;margin-bottom:3px;overflow:hidden}.score-bar-fill{height:100%;border-radius:2px;background:linear-gradient(90deg,#d72924,#df5450);width:0%;transition:width .6s cubic-bezier(.4,0,.2,1)}.score-label{font-size:10px;color:#666;display:flex;justify-content:space-between}.card-actions{display:flex;gap:6px;margin-top:6px}.btn{display:inline-flex;align-items:center;gap:4px;padding:6px 14px;border-radius:4px;font-size:12px;font-weight:600;font-family:"IBM Plex Serif",Georgia,serif;text-decoration:none;cursor:pointer;border:none;transition:transform .15s ease,background .2s ease,filter .2s ease}.btn:hover{filter:brightness(1.2);transform:scale(1.04)}.btn:active{transform:scale(.97)!important}.btn-primary{background:linear-gradient(to right,#d72924,#db3e3a 51%,#d72924);color:#fff}.btn-secondary{background:#222;color:silver}.btn-secondary:hover{background:#333;filter:brightness(1.2);color:#fff}.card-date{margin-left:auto;font-size:12px;font-weight:700;color:#d72924;background:#d7292414;padding:3px 10px;border:1px solid rgba(215,41,36,.2);transition:background .2s,border-color .2s}.card:hover .card-date{background:#d7292426;border-color:#d7292466}.card-topics{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:10px}.topic-tag{font-size:10px;padding:2px 7px;background:#222;color:silver}.date-group{margin-bottom:16px}.date-header{font-size:12px;font-weight:700;color:#d72924;text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid #333}.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(360px,1fr));gap:16px}.empty{text-align:center;padding:60px 20px;color:#666}.empty h2{font-size:18px;margin-bottom:6px;color:#fff}@keyframes shimmer{0%{background-position:-200px 0}to{background-position:calc(200px + 100%) 0}}.skeleton{background:linear-gradient(90deg,#1a1a1a 25%,#2a2a2a,#1a1a1a 75%);background-size:200px 100%;animation:shimmer 1.5s ease-in-out infinite;border-radius:2px}.skeleton-img{height:180px;width:100%}.skeleton-text{height:14px;margin-bottom:8px;width:80%}.skeleton-text-short{height:14px;margin-bottom:8px;width:50%}.modal-overlay{display:none;position:fixed;top:0;right:0;bottom:0;left:0;background:#000000d9;z-index:1000;justify-content:center;align-items:center;padding:20px;opacity:0;transition:opacity .25s ease}.modal-overlay.active{display:flex;opacity:1}.modal{background:#0c0c0cf5;border:1px solid #333;max-width:480px;width:100%;padding:28px;position:relative;box-shadow:0 0 40px #d7292426;transform:translateY(20px) scale(.95);transition:transform .25s ease,opacity .25s ease;opacity:0}.modal-overlay.active .modal{transform:translateY(0) scale(1);opacity:1}.modal-close{position:absolute;top:12px;right:16px;background:none;border:none;color:#666;font-size:22px;cursor:pointer;font-family:"IBM Plex Serif",Georgia,serif;transition:color .2s,transform .15s}.modal-close:hover{color:#d72924;transform:rotate(90deg)}.modal h2{font-size:18px;color:#d72924;margin-bottom:4px}.modal .parent{font-size:13px;color:silver;margin-bottom:12px}.modal-row{display:flex;gap:8px;margin-bottom:12px;flex-wrap:wrap}.modal-section{margin-bottom:14px}.modal-section h3{font-size:11px;font-weight:600;color:#666;text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}.owner-list{display:flex;flex-wrap:wrap;gap:6px}.owner-tag{font-size:12px;padding:4px 10px;background:#d7292414;color:#d72924;border:1px solid rgba(215,41,36,.2)}.owner-wiki{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;font-size:14px;background:#d7292414;color:#d72924;border:1px solid rgba(215,41,36,.2);text-decoration:none;transition:background .2s,transform .15s}.owner-wiki:hover{background:#d7292440;color:#df5450;transform:scale(1.1)}.modal-wiki{display:inline-block;margin-top:6px;padding:8px 16px;background:linear-gradient(to right,#d72924,#db3e3a 51%,#d72924);color:#fff;text-decoration:none;font-size:13px;font-weight:600;font-family:"IBM Plex Serif",Georgia,serif;transition:opacity .2s,transform .15s}.modal-wiki:hover{opacity:.85;color:#fff;transform:scale(1.03)}@media(max-width:640px){.header,.container{padding:14px}.grid{grid-template-columns:1fr}}
frontend/dist/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>News Digest</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,600;0,700;1,400&family=Lora:wght@400;600;700&display=swap" rel="stylesheet">
8
+ <script type="module" crossorigin src="/assets/index-BewHjeaj.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-DI9qLnC9.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>News Digest</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@0,400;0,600;0,700;1,400&family=Lora:wght@400;600;700&display=swap" rel="stylesheet">
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
frontend/package-lock.json ADDED
@@ -0,0 +1,1797 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "newsapp-frontend",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "newsapp-frontend",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "react": "^18.3.1",
12
+ "react-dom": "^18.3.1"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-react": "^4.3.4",
16
+ "vite": "^6.0.0"
17
+ }
18
+ },
19
+ "node_modules/@babel/code-frame": {
20
+ "version": "7.29.7",
21
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
22
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
23
+ "dev": true,
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@babel/helper-validator-identifier": "^7.29.7",
27
+ "js-tokens": "^4.0.0",
28
+ "picocolors": "^1.1.1"
29
+ },
30
+ "engines": {
31
+ "node": ">=6.9.0"
32
+ }
33
+ },
34
+ "node_modules/@babel/compat-data": {
35
+ "version": "7.29.7",
36
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
37
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
38
+ "dev": true,
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=6.9.0"
42
+ }
43
+ },
44
+ "node_modules/@babel/core": {
45
+ "version": "7.29.7",
46
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
47
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
48
+ "dev": true,
49
+ "license": "MIT",
50
+ "dependencies": {
51
+ "@babel/code-frame": "^7.29.7",
52
+ "@babel/generator": "^7.29.7",
53
+ "@babel/helper-compilation-targets": "^7.29.7",
54
+ "@babel/helper-module-transforms": "^7.29.7",
55
+ "@babel/helpers": "^7.29.7",
56
+ "@babel/parser": "^7.29.7",
57
+ "@babel/template": "^7.29.7",
58
+ "@babel/traverse": "^7.29.7",
59
+ "@babel/types": "^7.29.7",
60
+ "@jridgewell/remapping": "^2.3.5",
61
+ "convert-source-map": "^2.0.0",
62
+ "debug": "^4.1.0",
63
+ "gensync": "^1.0.0-beta.2",
64
+ "json5": "^2.2.3",
65
+ "semver": "^6.3.1"
66
+ },
67
+ "engines": {
68
+ "node": ">=6.9.0"
69
+ },
70
+ "funding": {
71
+ "type": "opencollective",
72
+ "url": "https://opencollective.com/babel"
73
+ }
74
+ },
75
+ "node_modules/@babel/generator": {
76
+ "version": "7.29.7",
77
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
78
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
79
+ "dev": true,
80
+ "license": "MIT",
81
+ "dependencies": {
82
+ "@babel/parser": "^7.29.7",
83
+ "@babel/types": "^7.29.7",
84
+ "@jridgewell/gen-mapping": "^0.3.12",
85
+ "@jridgewell/trace-mapping": "^0.3.28",
86
+ "jsesc": "^3.0.2"
87
+ },
88
+ "engines": {
89
+ "node": ">=6.9.0"
90
+ }
91
+ },
92
+ "node_modules/@babel/helper-compilation-targets": {
93
+ "version": "7.29.7",
94
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
95
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
96
+ "dev": true,
97
+ "license": "MIT",
98
+ "dependencies": {
99
+ "@babel/compat-data": "^7.29.7",
100
+ "@babel/helper-validator-option": "^7.29.7",
101
+ "browserslist": "^4.24.0",
102
+ "lru-cache": "^5.1.1",
103
+ "semver": "^6.3.1"
104
+ },
105
+ "engines": {
106
+ "node": ">=6.9.0"
107
+ }
108
+ },
109
+ "node_modules/@babel/helper-globals": {
110
+ "version": "7.29.7",
111
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
112
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
113
+ "dev": true,
114
+ "license": "MIT",
115
+ "engines": {
116
+ "node": ">=6.9.0"
117
+ }
118
+ },
119
+ "node_modules/@babel/helper-module-imports": {
120
+ "version": "7.29.7",
121
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
122
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
123
+ "dev": true,
124
+ "license": "MIT",
125
+ "dependencies": {
126
+ "@babel/traverse": "^7.29.7",
127
+ "@babel/types": "^7.29.7"
128
+ },
129
+ "engines": {
130
+ "node": ">=6.9.0"
131
+ }
132
+ },
133
+ "node_modules/@babel/helper-module-transforms": {
134
+ "version": "7.29.7",
135
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
136
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
137
+ "dev": true,
138
+ "license": "MIT",
139
+ "dependencies": {
140
+ "@babel/helper-module-imports": "^7.29.7",
141
+ "@babel/helper-validator-identifier": "^7.29.7",
142
+ "@babel/traverse": "^7.29.7"
143
+ },
144
+ "engines": {
145
+ "node": ">=6.9.0"
146
+ },
147
+ "peerDependencies": {
148
+ "@babel/core": "^7.0.0"
149
+ }
150
+ },
151
+ "node_modules/@babel/helper-plugin-utils": {
152
+ "version": "7.29.7",
153
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
154
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
155
+ "dev": true,
156
+ "license": "MIT",
157
+ "engines": {
158
+ "node": ">=6.9.0"
159
+ }
160
+ },
161
+ "node_modules/@babel/helper-string-parser": {
162
+ "version": "7.29.7",
163
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
164
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
165
+ "dev": true,
166
+ "license": "MIT",
167
+ "engines": {
168
+ "node": ">=6.9.0"
169
+ }
170
+ },
171
+ "node_modules/@babel/helper-validator-identifier": {
172
+ "version": "7.29.7",
173
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
174
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
175
+ "dev": true,
176
+ "license": "MIT",
177
+ "engines": {
178
+ "node": ">=6.9.0"
179
+ }
180
+ },
181
+ "node_modules/@babel/helper-validator-option": {
182
+ "version": "7.29.7",
183
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
184
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
185
+ "dev": true,
186
+ "license": "MIT",
187
+ "engines": {
188
+ "node": ">=6.9.0"
189
+ }
190
+ },
191
+ "node_modules/@babel/helpers": {
192
+ "version": "7.29.7",
193
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
194
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
195
+ "dev": true,
196
+ "license": "MIT",
197
+ "dependencies": {
198
+ "@babel/template": "^7.29.7",
199
+ "@babel/types": "^7.29.7"
200
+ },
201
+ "engines": {
202
+ "node": ">=6.9.0"
203
+ }
204
+ },
205
+ "node_modules/@babel/parser": {
206
+ "version": "7.29.7",
207
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
208
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
209
+ "dev": true,
210
+ "license": "MIT",
211
+ "dependencies": {
212
+ "@babel/types": "^7.29.7"
213
+ },
214
+ "bin": {
215
+ "parser": "bin/babel-parser.js"
216
+ },
217
+ "engines": {
218
+ "node": ">=6.0.0"
219
+ }
220
+ },
221
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
222
+ "version": "7.29.7",
223
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
224
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
225
+ "dev": true,
226
+ "license": "MIT",
227
+ "dependencies": {
228
+ "@babel/helper-plugin-utils": "^7.29.7"
229
+ },
230
+ "engines": {
231
+ "node": ">=6.9.0"
232
+ },
233
+ "peerDependencies": {
234
+ "@babel/core": "^7.0.0-0"
235
+ }
236
+ },
237
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
238
+ "version": "7.29.7",
239
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
240
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
241
+ "dev": true,
242
+ "license": "MIT",
243
+ "dependencies": {
244
+ "@babel/helper-plugin-utils": "^7.29.7"
245
+ },
246
+ "engines": {
247
+ "node": ">=6.9.0"
248
+ },
249
+ "peerDependencies": {
250
+ "@babel/core": "^7.0.0-0"
251
+ }
252
+ },
253
+ "node_modules/@babel/template": {
254
+ "version": "7.29.7",
255
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
256
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
257
+ "dev": true,
258
+ "license": "MIT",
259
+ "dependencies": {
260
+ "@babel/code-frame": "^7.29.7",
261
+ "@babel/parser": "^7.29.7",
262
+ "@babel/types": "^7.29.7"
263
+ },
264
+ "engines": {
265
+ "node": ">=6.9.0"
266
+ }
267
+ },
268
+ "node_modules/@babel/traverse": {
269
+ "version": "7.29.7",
270
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
271
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
272
+ "dev": true,
273
+ "license": "MIT",
274
+ "dependencies": {
275
+ "@babel/code-frame": "^7.29.7",
276
+ "@babel/generator": "^7.29.7",
277
+ "@babel/helper-globals": "^7.29.7",
278
+ "@babel/parser": "^7.29.7",
279
+ "@babel/template": "^7.29.7",
280
+ "@babel/types": "^7.29.7",
281
+ "debug": "^4.3.1"
282
+ },
283
+ "engines": {
284
+ "node": ">=6.9.0"
285
+ }
286
+ },
287
+ "node_modules/@babel/types": {
288
+ "version": "7.29.7",
289
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
290
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
291
+ "dev": true,
292
+ "license": "MIT",
293
+ "dependencies": {
294
+ "@babel/helper-string-parser": "^7.29.7",
295
+ "@babel/helper-validator-identifier": "^7.29.7"
296
+ },
297
+ "engines": {
298
+ "node": ">=6.9.0"
299
+ }
300
+ },
301
+ "node_modules/@esbuild/aix-ppc64": {
302
+ "version": "0.25.12",
303
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
304
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
305
+ "cpu": [
306
+ "ppc64"
307
+ ],
308
+ "dev": true,
309
+ "license": "MIT",
310
+ "optional": true,
311
+ "os": [
312
+ "aix"
313
+ ],
314
+ "engines": {
315
+ "node": ">=18"
316
+ }
317
+ },
318
+ "node_modules/@esbuild/android-arm": {
319
+ "version": "0.25.12",
320
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
321
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
322
+ "cpu": [
323
+ "arm"
324
+ ],
325
+ "dev": true,
326
+ "license": "MIT",
327
+ "optional": true,
328
+ "os": [
329
+ "android"
330
+ ],
331
+ "engines": {
332
+ "node": ">=18"
333
+ }
334
+ },
335
+ "node_modules/@esbuild/android-arm64": {
336
+ "version": "0.25.12",
337
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
338
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
339
+ "cpu": [
340
+ "arm64"
341
+ ],
342
+ "dev": true,
343
+ "license": "MIT",
344
+ "optional": true,
345
+ "os": [
346
+ "android"
347
+ ],
348
+ "engines": {
349
+ "node": ">=18"
350
+ }
351
+ },
352
+ "node_modules/@esbuild/android-x64": {
353
+ "version": "0.25.12",
354
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
355
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
356
+ "cpu": [
357
+ "x64"
358
+ ],
359
+ "dev": true,
360
+ "license": "MIT",
361
+ "optional": true,
362
+ "os": [
363
+ "android"
364
+ ],
365
+ "engines": {
366
+ "node": ">=18"
367
+ }
368
+ },
369
+ "node_modules/@esbuild/darwin-arm64": {
370
+ "version": "0.25.12",
371
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
372
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
373
+ "cpu": [
374
+ "arm64"
375
+ ],
376
+ "dev": true,
377
+ "license": "MIT",
378
+ "optional": true,
379
+ "os": [
380
+ "darwin"
381
+ ],
382
+ "engines": {
383
+ "node": ">=18"
384
+ }
385
+ },
386
+ "node_modules/@esbuild/darwin-x64": {
387
+ "version": "0.25.12",
388
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
389
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
390
+ "cpu": [
391
+ "x64"
392
+ ],
393
+ "dev": true,
394
+ "license": "MIT",
395
+ "optional": true,
396
+ "os": [
397
+ "darwin"
398
+ ],
399
+ "engines": {
400
+ "node": ">=18"
401
+ }
402
+ },
403
+ "node_modules/@esbuild/freebsd-arm64": {
404
+ "version": "0.25.12",
405
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
406
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
407
+ "cpu": [
408
+ "arm64"
409
+ ],
410
+ "dev": true,
411
+ "license": "MIT",
412
+ "optional": true,
413
+ "os": [
414
+ "freebsd"
415
+ ],
416
+ "engines": {
417
+ "node": ">=18"
418
+ }
419
+ },
420
+ "node_modules/@esbuild/freebsd-x64": {
421
+ "version": "0.25.12",
422
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
423
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
424
+ "cpu": [
425
+ "x64"
426
+ ],
427
+ "dev": true,
428
+ "license": "MIT",
429
+ "optional": true,
430
+ "os": [
431
+ "freebsd"
432
+ ],
433
+ "engines": {
434
+ "node": ">=18"
435
+ }
436
+ },
437
+ "node_modules/@esbuild/linux-arm": {
438
+ "version": "0.25.12",
439
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
440
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
441
+ "cpu": [
442
+ "arm"
443
+ ],
444
+ "dev": true,
445
+ "license": "MIT",
446
+ "optional": true,
447
+ "os": [
448
+ "linux"
449
+ ],
450
+ "engines": {
451
+ "node": ">=18"
452
+ }
453
+ },
454
+ "node_modules/@esbuild/linux-arm64": {
455
+ "version": "0.25.12",
456
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
457
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
458
+ "cpu": [
459
+ "arm64"
460
+ ],
461
+ "dev": true,
462
+ "license": "MIT",
463
+ "optional": true,
464
+ "os": [
465
+ "linux"
466
+ ],
467
+ "engines": {
468
+ "node": ">=18"
469
+ }
470
+ },
471
+ "node_modules/@esbuild/linux-ia32": {
472
+ "version": "0.25.12",
473
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
474
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
475
+ "cpu": [
476
+ "ia32"
477
+ ],
478
+ "dev": true,
479
+ "license": "MIT",
480
+ "optional": true,
481
+ "os": [
482
+ "linux"
483
+ ],
484
+ "engines": {
485
+ "node": ">=18"
486
+ }
487
+ },
488
+ "node_modules/@esbuild/linux-loong64": {
489
+ "version": "0.25.12",
490
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
491
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
492
+ "cpu": [
493
+ "loong64"
494
+ ],
495
+ "dev": true,
496
+ "license": "MIT",
497
+ "optional": true,
498
+ "os": [
499
+ "linux"
500
+ ],
501
+ "engines": {
502
+ "node": ">=18"
503
+ }
504
+ },
505
+ "node_modules/@esbuild/linux-mips64el": {
506
+ "version": "0.25.12",
507
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
508
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
509
+ "cpu": [
510
+ "mips64el"
511
+ ],
512
+ "dev": true,
513
+ "license": "MIT",
514
+ "optional": true,
515
+ "os": [
516
+ "linux"
517
+ ],
518
+ "engines": {
519
+ "node": ">=18"
520
+ }
521
+ },
522
+ "node_modules/@esbuild/linux-ppc64": {
523
+ "version": "0.25.12",
524
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
525
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
526
+ "cpu": [
527
+ "ppc64"
528
+ ],
529
+ "dev": true,
530
+ "license": "MIT",
531
+ "optional": true,
532
+ "os": [
533
+ "linux"
534
+ ],
535
+ "engines": {
536
+ "node": ">=18"
537
+ }
538
+ },
539
+ "node_modules/@esbuild/linux-riscv64": {
540
+ "version": "0.25.12",
541
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
542
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
543
+ "cpu": [
544
+ "riscv64"
545
+ ],
546
+ "dev": true,
547
+ "license": "MIT",
548
+ "optional": true,
549
+ "os": [
550
+ "linux"
551
+ ],
552
+ "engines": {
553
+ "node": ">=18"
554
+ }
555
+ },
556
+ "node_modules/@esbuild/linux-s390x": {
557
+ "version": "0.25.12",
558
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
559
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
560
+ "cpu": [
561
+ "s390x"
562
+ ],
563
+ "dev": true,
564
+ "license": "MIT",
565
+ "optional": true,
566
+ "os": [
567
+ "linux"
568
+ ],
569
+ "engines": {
570
+ "node": ">=18"
571
+ }
572
+ },
573
+ "node_modules/@esbuild/linux-x64": {
574
+ "version": "0.25.12",
575
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
576
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
577
+ "cpu": [
578
+ "x64"
579
+ ],
580
+ "dev": true,
581
+ "license": "MIT",
582
+ "optional": true,
583
+ "os": [
584
+ "linux"
585
+ ],
586
+ "engines": {
587
+ "node": ">=18"
588
+ }
589
+ },
590
+ "node_modules/@esbuild/netbsd-arm64": {
591
+ "version": "0.25.12",
592
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
593
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
594
+ "cpu": [
595
+ "arm64"
596
+ ],
597
+ "dev": true,
598
+ "license": "MIT",
599
+ "optional": true,
600
+ "os": [
601
+ "netbsd"
602
+ ],
603
+ "engines": {
604
+ "node": ">=18"
605
+ }
606
+ },
607
+ "node_modules/@esbuild/netbsd-x64": {
608
+ "version": "0.25.12",
609
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
610
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
611
+ "cpu": [
612
+ "x64"
613
+ ],
614
+ "dev": true,
615
+ "license": "MIT",
616
+ "optional": true,
617
+ "os": [
618
+ "netbsd"
619
+ ],
620
+ "engines": {
621
+ "node": ">=18"
622
+ }
623
+ },
624
+ "node_modules/@esbuild/openbsd-arm64": {
625
+ "version": "0.25.12",
626
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
627
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
628
+ "cpu": [
629
+ "arm64"
630
+ ],
631
+ "dev": true,
632
+ "license": "MIT",
633
+ "optional": true,
634
+ "os": [
635
+ "openbsd"
636
+ ],
637
+ "engines": {
638
+ "node": ">=18"
639
+ }
640
+ },
641
+ "node_modules/@esbuild/openbsd-x64": {
642
+ "version": "0.25.12",
643
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
644
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
645
+ "cpu": [
646
+ "x64"
647
+ ],
648
+ "dev": true,
649
+ "license": "MIT",
650
+ "optional": true,
651
+ "os": [
652
+ "openbsd"
653
+ ],
654
+ "engines": {
655
+ "node": ">=18"
656
+ }
657
+ },
658
+ "node_modules/@esbuild/openharmony-arm64": {
659
+ "version": "0.25.12",
660
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
661
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
662
+ "cpu": [
663
+ "arm64"
664
+ ],
665
+ "dev": true,
666
+ "license": "MIT",
667
+ "optional": true,
668
+ "os": [
669
+ "openharmony"
670
+ ],
671
+ "engines": {
672
+ "node": ">=18"
673
+ }
674
+ },
675
+ "node_modules/@esbuild/sunos-x64": {
676
+ "version": "0.25.12",
677
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
678
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
679
+ "cpu": [
680
+ "x64"
681
+ ],
682
+ "dev": true,
683
+ "license": "MIT",
684
+ "optional": true,
685
+ "os": [
686
+ "sunos"
687
+ ],
688
+ "engines": {
689
+ "node": ">=18"
690
+ }
691
+ },
692
+ "node_modules/@esbuild/win32-arm64": {
693
+ "version": "0.25.12",
694
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
695
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
696
+ "cpu": [
697
+ "arm64"
698
+ ],
699
+ "dev": true,
700
+ "license": "MIT",
701
+ "optional": true,
702
+ "os": [
703
+ "win32"
704
+ ],
705
+ "engines": {
706
+ "node": ">=18"
707
+ }
708
+ },
709
+ "node_modules/@esbuild/win32-ia32": {
710
+ "version": "0.25.12",
711
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
712
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
713
+ "cpu": [
714
+ "ia32"
715
+ ],
716
+ "dev": true,
717
+ "license": "MIT",
718
+ "optional": true,
719
+ "os": [
720
+ "win32"
721
+ ],
722
+ "engines": {
723
+ "node": ">=18"
724
+ }
725
+ },
726
+ "node_modules/@esbuild/win32-x64": {
727
+ "version": "0.25.12",
728
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
729
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
730
+ "cpu": [
731
+ "x64"
732
+ ],
733
+ "dev": true,
734
+ "license": "MIT",
735
+ "optional": true,
736
+ "os": [
737
+ "win32"
738
+ ],
739
+ "engines": {
740
+ "node": ">=18"
741
+ }
742
+ },
743
+ "node_modules/@jridgewell/gen-mapping": {
744
+ "version": "0.3.13",
745
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
746
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
747
+ "dev": true,
748
+ "license": "MIT",
749
+ "dependencies": {
750
+ "@jridgewell/sourcemap-codec": "^1.5.0",
751
+ "@jridgewell/trace-mapping": "^0.3.24"
752
+ }
753
+ },
754
+ "node_modules/@jridgewell/remapping": {
755
+ "version": "2.3.5",
756
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
757
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
758
+ "dev": true,
759
+ "license": "MIT",
760
+ "dependencies": {
761
+ "@jridgewell/gen-mapping": "^0.3.5",
762
+ "@jridgewell/trace-mapping": "^0.3.24"
763
+ }
764
+ },
765
+ "node_modules/@jridgewell/resolve-uri": {
766
+ "version": "3.1.2",
767
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
768
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
769
+ "dev": true,
770
+ "license": "MIT",
771
+ "engines": {
772
+ "node": ">=6.0.0"
773
+ }
774
+ },
775
+ "node_modules/@jridgewell/sourcemap-codec": {
776
+ "version": "1.5.5",
777
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
778
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
779
+ "dev": true,
780
+ "license": "MIT"
781
+ },
782
+ "node_modules/@jridgewell/trace-mapping": {
783
+ "version": "0.3.31",
784
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
785
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
786
+ "dev": true,
787
+ "license": "MIT",
788
+ "dependencies": {
789
+ "@jridgewell/resolve-uri": "^3.1.0",
790
+ "@jridgewell/sourcemap-codec": "^1.4.14"
791
+ }
792
+ },
793
+ "node_modules/@rolldown/pluginutils": {
794
+ "version": "1.0.0-beta.27",
795
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
796
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
797
+ "dev": true,
798
+ "license": "MIT"
799
+ },
800
+ "node_modules/@rollup/rollup-android-arm-eabi": {
801
+ "version": "4.62.2",
802
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
803
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
804
+ "cpu": [
805
+ "arm"
806
+ ],
807
+ "dev": true,
808
+ "license": "MIT",
809
+ "optional": true,
810
+ "os": [
811
+ "android"
812
+ ]
813
+ },
814
+ "node_modules/@rollup/rollup-android-arm64": {
815
+ "version": "4.62.2",
816
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
817
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
818
+ "cpu": [
819
+ "arm64"
820
+ ],
821
+ "dev": true,
822
+ "license": "MIT",
823
+ "optional": true,
824
+ "os": [
825
+ "android"
826
+ ]
827
+ },
828
+ "node_modules/@rollup/rollup-darwin-arm64": {
829
+ "version": "4.62.2",
830
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
831
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
832
+ "cpu": [
833
+ "arm64"
834
+ ],
835
+ "dev": true,
836
+ "license": "MIT",
837
+ "optional": true,
838
+ "os": [
839
+ "darwin"
840
+ ]
841
+ },
842
+ "node_modules/@rollup/rollup-darwin-x64": {
843
+ "version": "4.62.2",
844
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
845
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
846
+ "cpu": [
847
+ "x64"
848
+ ],
849
+ "dev": true,
850
+ "license": "MIT",
851
+ "optional": true,
852
+ "os": [
853
+ "darwin"
854
+ ]
855
+ },
856
+ "node_modules/@rollup/rollup-freebsd-arm64": {
857
+ "version": "4.62.2",
858
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
859
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
860
+ "cpu": [
861
+ "arm64"
862
+ ],
863
+ "dev": true,
864
+ "license": "MIT",
865
+ "optional": true,
866
+ "os": [
867
+ "freebsd"
868
+ ]
869
+ },
870
+ "node_modules/@rollup/rollup-freebsd-x64": {
871
+ "version": "4.62.2",
872
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
873
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
874
+ "cpu": [
875
+ "x64"
876
+ ],
877
+ "dev": true,
878
+ "license": "MIT",
879
+ "optional": true,
880
+ "os": [
881
+ "freebsd"
882
+ ]
883
+ },
884
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
885
+ "version": "4.62.2",
886
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
887
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
888
+ "cpu": [
889
+ "arm"
890
+ ],
891
+ "dev": true,
892
+ "license": "MIT",
893
+ "optional": true,
894
+ "os": [
895
+ "linux"
896
+ ]
897
+ },
898
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
899
+ "version": "4.62.2",
900
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
901
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
902
+ "cpu": [
903
+ "arm"
904
+ ],
905
+ "dev": true,
906
+ "license": "MIT",
907
+ "optional": true,
908
+ "os": [
909
+ "linux"
910
+ ]
911
+ },
912
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
913
+ "version": "4.62.2",
914
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
915
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
916
+ "cpu": [
917
+ "arm64"
918
+ ],
919
+ "dev": true,
920
+ "license": "MIT",
921
+ "optional": true,
922
+ "os": [
923
+ "linux"
924
+ ]
925
+ },
926
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
927
+ "version": "4.62.2",
928
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
929
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
930
+ "cpu": [
931
+ "arm64"
932
+ ],
933
+ "dev": true,
934
+ "license": "MIT",
935
+ "optional": true,
936
+ "os": [
937
+ "linux"
938
+ ]
939
+ },
940
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
941
+ "version": "4.62.2",
942
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
943
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
944
+ "cpu": [
945
+ "loong64"
946
+ ],
947
+ "dev": true,
948
+ "license": "MIT",
949
+ "optional": true,
950
+ "os": [
951
+ "linux"
952
+ ]
953
+ },
954
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
955
+ "version": "4.62.2",
956
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
957
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
958
+ "cpu": [
959
+ "loong64"
960
+ ],
961
+ "dev": true,
962
+ "license": "MIT",
963
+ "optional": true,
964
+ "os": [
965
+ "linux"
966
+ ]
967
+ },
968
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
969
+ "version": "4.62.2",
970
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
971
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
972
+ "cpu": [
973
+ "ppc64"
974
+ ],
975
+ "dev": true,
976
+ "license": "MIT",
977
+ "optional": true,
978
+ "os": [
979
+ "linux"
980
+ ]
981
+ },
982
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
983
+ "version": "4.62.2",
984
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
985
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
986
+ "cpu": [
987
+ "ppc64"
988
+ ],
989
+ "dev": true,
990
+ "license": "MIT",
991
+ "optional": true,
992
+ "os": [
993
+ "linux"
994
+ ]
995
+ },
996
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
997
+ "version": "4.62.2",
998
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
999
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
1000
+ "cpu": [
1001
+ "riscv64"
1002
+ ],
1003
+ "dev": true,
1004
+ "license": "MIT",
1005
+ "optional": true,
1006
+ "os": [
1007
+ "linux"
1008
+ ]
1009
+ },
1010
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
1011
+ "version": "4.62.2",
1012
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
1013
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
1014
+ "cpu": [
1015
+ "riscv64"
1016
+ ],
1017
+ "dev": true,
1018
+ "license": "MIT",
1019
+ "optional": true,
1020
+ "os": [
1021
+ "linux"
1022
+ ]
1023
+ },
1024
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
1025
+ "version": "4.62.2",
1026
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
1027
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
1028
+ "cpu": [
1029
+ "s390x"
1030
+ ],
1031
+ "dev": true,
1032
+ "license": "MIT",
1033
+ "optional": true,
1034
+ "os": [
1035
+ "linux"
1036
+ ]
1037
+ },
1038
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
1039
+ "version": "4.62.2",
1040
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
1041
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
1042
+ "cpu": [
1043
+ "x64"
1044
+ ],
1045
+ "dev": true,
1046
+ "license": "MIT",
1047
+ "optional": true,
1048
+ "os": [
1049
+ "linux"
1050
+ ]
1051
+ },
1052
+ "node_modules/@rollup/rollup-linux-x64-musl": {
1053
+ "version": "4.62.2",
1054
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
1055
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
1056
+ "cpu": [
1057
+ "x64"
1058
+ ],
1059
+ "dev": true,
1060
+ "license": "MIT",
1061
+ "optional": true,
1062
+ "os": [
1063
+ "linux"
1064
+ ]
1065
+ },
1066
+ "node_modules/@rollup/rollup-openbsd-x64": {
1067
+ "version": "4.62.2",
1068
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
1069
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
1070
+ "cpu": [
1071
+ "x64"
1072
+ ],
1073
+ "dev": true,
1074
+ "license": "MIT",
1075
+ "optional": true,
1076
+ "os": [
1077
+ "openbsd"
1078
+ ]
1079
+ },
1080
+ "node_modules/@rollup/rollup-openharmony-arm64": {
1081
+ "version": "4.62.2",
1082
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
1083
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
1084
+ "cpu": [
1085
+ "arm64"
1086
+ ],
1087
+ "dev": true,
1088
+ "license": "MIT",
1089
+ "optional": true,
1090
+ "os": [
1091
+ "openharmony"
1092
+ ]
1093
+ },
1094
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1095
+ "version": "4.62.2",
1096
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
1097
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
1098
+ "cpu": [
1099
+ "arm64"
1100
+ ],
1101
+ "dev": true,
1102
+ "license": "MIT",
1103
+ "optional": true,
1104
+ "os": [
1105
+ "win32"
1106
+ ]
1107
+ },
1108
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1109
+ "version": "4.62.2",
1110
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
1111
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
1112
+ "cpu": [
1113
+ "ia32"
1114
+ ],
1115
+ "dev": true,
1116
+ "license": "MIT",
1117
+ "optional": true,
1118
+ "os": [
1119
+ "win32"
1120
+ ]
1121
+ },
1122
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1123
+ "version": "4.62.2",
1124
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
1125
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
1126
+ "cpu": [
1127
+ "x64"
1128
+ ],
1129
+ "dev": true,
1130
+ "license": "MIT",
1131
+ "optional": true,
1132
+ "os": [
1133
+ "win32"
1134
+ ]
1135
+ },
1136
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1137
+ "version": "4.62.2",
1138
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
1139
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
1140
+ "cpu": [
1141
+ "x64"
1142
+ ],
1143
+ "dev": true,
1144
+ "license": "MIT",
1145
+ "optional": true,
1146
+ "os": [
1147
+ "win32"
1148
+ ]
1149
+ },
1150
+ "node_modules/@types/babel__core": {
1151
+ "version": "7.20.5",
1152
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
1153
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
1154
+ "dev": true,
1155
+ "license": "MIT",
1156
+ "dependencies": {
1157
+ "@babel/parser": "^7.20.7",
1158
+ "@babel/types": "^7.20.7",
1159
+ "@types/babel__generator": "*",
1160
+ "@types/babel__template": "*",
1161
+ "@types/babel__traverse": "*"
1162
+ }
1163
+ },
1164
+ "node_modules/@types/babel__generator": {
1165
+ "version": "7.27.0",
1166
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
1167
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
1168
+ "dev": true,
1169
+ "license": "MIT",
1170
+ "dependencies": {
1171
+ "@babel/types": "^7.0.0"
1172
+ }
1173
+ },
1174
+ "node_modules/@types/babel__template": {
1175
+ "version": "7.4.4",
1176
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
1177
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
1178
+ "dev": true,
1179
+ "license": "MIT",
1180
+ "dependencies": {
1181
+ "@babel/parser": "^7.1.0",
1182
+ "@babel/types": "^7.0.0"
1183
+ }
1184
+ },
1185
+ "node_modules/@types/babel__traverse": {
1186
+ "version": "7.28.0",
1187
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
1188
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
1189
+ "dev": true,
1190
+ "license": "MIT",
1191
+ "dependencies": {
1192
+ "@babel/types": "^7.28.2"
1193
+ }
1194
+ },
1195
+ "node_modules/@types/estree": {
1196
+ "version": "1.0.9",
1197
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
1198
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1199
+ "dev": true,
1200
+ "license": "MIT"
1201
+ },
1202
+ "node_modules/@vitejs/plugin-react": {
1203
+ "version": "4.7.0",
1204
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
1205
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
1206
+ "dev": true,
1207
+ "license": "MIT",
1208
+ "dependencies": {
1209
+ "@babel/core": "^7.28.0",
1210
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
1211
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
1212
+ "@rolldown/pluginutils": "1.0.0-beta.27",
1213
+ "@types/babel__core": "^7.20.5",
1214
+ "react-refresh": "^0.17.0"
1215
+ },
1216
+ "engines": {
1217
+ "node": "^14.18.0 || >=16.0.0"
1218
+ },
1219
+ "peerDependencies": {
1220
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
1221
+ }
1222
+ },
1223
+ "node_modules/baseline-browser-mapping": {
1224
+ "version": "2.10.43",
1225
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
1226
+ "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
1227
+ "dev": true,
1228
+ "license": "Apache-2.0",
1229
+ "bin": {
1230
+ "baseline-browser-mapping": "dist/cli.cjs"
1231
+ },
1232
+ "engines": {
1233
+ "node": ">=6.0.0"
1234
+ }
1235
+ },
1236
+ "node_modules/browserslist": {
1237
+ "version": "4.28.6",
1238
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz",
1239
+ "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==",
1240
+ "dev": true,
1241
+ "funding": [
1242
+ {
1243
+ "type": "opencollective",
1244
+ "url": "https://opencollective.com/browserslist"
1245
+ },
1246
+ {
1247
+ "type": "tidelift",
1248
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1249
+ },
1250
+ {
1251
+ "type": "github",
1252
+ "url": "https://github.com/sponsors/ai"
1253
+ }
1254
+ ],
1255
+ "license": "MIT",
1256
+ "dependencies": {
1257
+ "baseline-browser-mapping": "^2.10.42",
1258
+ "caniuse-lite": "^1.0.30001803",
1259
+ "electron-to-chromium": "^1.5.389",
1260
+ "node-releases": "^2.0.51",
1261
+ "update-browserslist-db": "^1.2.3"
1262
+ },
1263
+ "bin": {
1264
+ "browserslist": "cli.js"
1265
+ },
1266
+ "engines": {
1267
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
1268
+ }
1269
+ },
1270
+ "node_modules/caniuse-lite": {
1271
+ "version": "1.0.30001805",
1272
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz",
1273
+ "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==",
1274
+ "dev": true,
1275
+ "funding": [
1276
+ {
1277
+ "type": "opencollective",
1278
+ "url": "https://opencollective.com/browserslist"
1279
+ },
1280
+ {
1281
+ "type": "tidelift",
1282
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
1283
+ },
1284
+ {
1285
+ "type": "github",
1286
+ "url": "https://github.com/sponsors/ai"
1287
+ }
1288
+ ],
1289
+ "license": "CC-BY-4.0"
1290
+ },
1291
+ "node_modules/convert-source-map": {
1292
+ "version": "2.0.0",
1293
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
1294
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
1295
+ "dev": true,
1296
+ "license": "MIT"
1297
+ },
1298
+ "node_modules/debug": {
1299
+ "version": "4.4.3",
1300
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
1301
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
1302
+ "dev": true,
1303
+ "license": "MIT",
1304
+ "dependencies": {
1305
+ "ms": "^2.1.3"
1306
+ },
1307
+ "engines": {
1308
+ "node": ">=6.0"
1309
+ },
1310
+ "peerDependenciesMeta": {
1311
+ "supports-color": {
1312
+ "optional": true
1313
+ }
1314
+ }
1315
+ },
1316
+ "node_modules/electron-to-chromium": {
1317
+ "version": "1.5.389",
1318
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz",
1319
+ "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==",
1320
+ "dev": true,
1321
+ "license": "ISC"
1322
+ },
1323
+ "node_modules/esbuild": {
1324
+ "version": "0.25.12",
1325
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
1326
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
1327
+ "dev": true,
1328
+ "hasInstallScript": true,
1329
+ "license": "MIT",
1330
+ "bin": {
1331
+ "esbuild": "bin/esbuild"
1332
+ },
1333
+ "engines": {
1334
+ "node": ">=18"
1335
+ },
1336
+ "optionalDependencies": {
1337
+ "@esbuild/aix-ppc64": "0.25.12",
1338
+ "@esbuild/android-arm": "0.25.12",
1339
+ "@esbuild/android-arm64": "0.25.12",
1340
+ "@esbuild/android-x64": "0.25.12",
1341
+ "@esbuild/darwin-arm64": "0.25.12",
1342
+ "@esbuild/darwin-x64": "0.25.12",
1343
+ "@esbuild/freebsd-arm64": "0.25.12",
1344
+ "@esbuild/freebsd-x64": "0.25.12",
1345
+ "@esbuild/linux-arm": "0.25.12",
1346
+ "@esbuild/linux-arm64": "0.25.12",
1347
+ "@esbuild/linux-ia32": "0.25.12",
1348
+ "@esbuild/linux-loong64": "0.25.12",
1349
+ "@esbuild/linux-mips64el": "0.25.12",
1350
+ "@esbuild/linux-ppc64": "0.25.12",
1351
+ "@esbuild/linux-riscv64": "0.25.12",
1352
+ "@esbuild/linux-s390x": "0.25.12",
1353
+ "@esbuild/linux-x64": "0.25.12",
1354
+ "@esbuild/netbsd-arm64": "0.25.12",
1355
+ "@esbuild/netbsd-x64": "0.25.12",
1356
+ "@esbuild/openbsd-arm64": "0.25.12",
1357
+ "@esbuild/openbsd-x64": "0.25.12",
1358
+ "@esbuild/openharmony-arm64": "0.25.12",
1359
+ "@esbuild/sunos-x64": "0.25.12",
1360
+ "@esbuild/win32-arm64": "0.25.12",
1361
+ "@esbuild/win32-ia32": "0.25.12",
1362
+ "@esbuild/win32-x64": "0.25.12"
1363
+ }
1364
+ },
1365
+ "node_modules/escalade": {
1366
+ "version": "3.2.0",
1367
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
1368
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
1369
+ "dev": true,
1370
+ "license": "MIT",
1371
+ "engines": {
1372
+ "node": ">=6"
1373
+ }
1374
+ },
1375
+ "node_modules/fdir": {
1376
+ "version": "6.5.0",
1377
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1378
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1379
+ "dev": true,
1380
+ "license": "MIT",
1381
+ "engines": {
1382
+ "node": ">=12.0.0"
1383
+ },
1384
+ "peerDependencies": {
1385
+ "picomatch": "^3 || ^4"
1386
+ },
1387
+ "peerDependenciesMeta": {
1388
+ "picomatch": {
1389
+ "optional": true
1390
+ }
1391
+ }
1392
+ },
1393
+ "node_modules/fsevents": {
1394
+ "version": "2.3.3",
1395
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1396
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1397
+ "dev": true,
1398
+ "hasInstallScript": true,
1399
+ "license": "MIT",
1400
+ "optional": true,
1401
+ "os": [
1402
+ "darwin"
1403
+ ],
1404
+ "engines": {
1405
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1406
+ }
1407
+ },
1408
+ "node_modules/gensync": {
1409
+ "version": "1.0.0-beta.2",
1410
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
1411
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
1412
+ "dev": true,
1413
+ "license": "MIT",
1414
+ "engines": {
1415
+ "node": ">=6.9.0"
1416
+ }
1417
+ },
1418
+ "node_modules/js-tokens": {
1419
+ "version": "4.0.0",
1420
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
1421
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
1422
+ "license": "MIT"
1423
+ },
1424
+ "node_modules/jsesc": {
1425
+ "version": "3.1.0",
1426
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
1427
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
1428
+ "dev": true,
1429
+ "license": "MIT",
1430
+ "bin": {
1431
+ "jsesc": "bin/jsesc"
1432
+ },
1433
+ "engines": {
1434
+ "node": ">=6"
1435
+ }
1436
+ },
1437
+ "node_modules/json5": {
1438
+ "version": "2.2.3",
1439
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
1440
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
1441
+ "dev": true,
1442
+ "license": "MIT",
1443
+ "bin": {
1444
+ "json5": "lib/cli.js"
1445
+ },
1446
+ "engines": {
1447
+ "node": ">=6"
1448
+ }
1449
+ },
1450
+ "node_modules/loose-envify": {
1451
+ "version": "1.4.0",
1452
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
1453
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
1454
+ "license": "MIT",
1455
+ "dependencies": {
1456
+ "js-tokens": "^3.0.0 || ^4.0.0"
1457
+ },
1458
+ "bin": {
1459
+ "loose-envify": "cli.js"
1460
+ }
1461
+ },
1462
+ "node_modules/lru-cache": {
1463
+ "version": "5.1.1",
1464
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
1465
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
1466
+ "dev": true,
1467
+ "license": "ISC",
1468
+ "dependencies": {
1469
+ "yallist": "^3.0.2"
1470
+ }
1471
+ },
1472
+ "node_modules/ms": {
1473
+ "version": "2.1.3",
1474
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1475
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1476
+ "dev": true,
1477
+ "license": "MIT"
1478
+ },
1479
+ "node_modules/nanoid": {
1480
+ "version": "3.3.16",
1481
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
1482
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
1483
+ "dev": true,
1484
+ "funding": [
1485
+ {
1486
+ "type": "github",
1487
+ "url": "https://github.com/sponsors/ai"
1488
+ }
1489
+ ],
1490
+ "license": "MIT",
1491
+ "bin": {
1492
+ "nanoid": "bin/nanoid.cjs"
1493
+ },
1494
+ "engines": {
1495
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1496
+ }
1497
+ },
1498
+ "node_modules/node-releases": {
1499
+ "version": "2.0.51",
1500
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
1501
+ "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
1502
+ "dev": true,
1503
+ "license": "MIT",
1504
+ "engines": {
1505
+ "node": ">=18"
1506
+ }
1507
+ },
1508
+ "node_modules/picocolors": {
1509
+ "version": "1.1.1",
1510
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1511
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1512
+ "dev": true,
1513
+ "license": "ISC"
1514
+ },
1515
+ "node_modules/picomatch": {
1516
+ "version": "4.0.5",
1517
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
1518
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
1519
+ "dev": true,
1520
+ "license": "MIT",
1521
+ "engines": {
1522
+ "node": ">=12"
1523
+ },
1524
+ "funding": {
1525
+ "url": "https://github.com/sponsors/jonschlinkert"
1526
+ }
1527
+ },
1528
+ "node_modules/postcss": {
1529
+ "version": "8.5.18",
1530
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.18.tgz",
1531
+ "integrity": "sha512-xdB1oSLHbz1vRWgCDalrCqEFTWzFlhqFC5tIHLMOSUIjhm3XXQ1qrFy8S/ESr1JYRRXqM3c1QFiMZUJdUTqyMQ==",
1532
+ "dev": true,
1533
+ "funding": [
1534
+ {
1535
+ "type": "opencollective",
1536
+ "url": "https://opencollective.com/postcss/"
1537
+ },
1538
+ {
1539
+ "type": "tidelift",
1540
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1541
+ },
1542
+ {
1543
+ "type": "github",
1544
+ "url": "https://github.com/sponsors/ai"
1545
+ }
1546
+ ],
1547
+ "license": "MIT",
1548
+ "dependencies": {
1549
+ "nanoid": "^3.3.12",
1550
+ "picocolors": "^1.1.1",
1551
+ "source-map-js": "^1.2.1"
1552
+ },
1553
+ "engines": {
1554
+ "node": "^10 || ^12 || >=14"
1555
+ }
1556
+ },
1557
+ "node_modules/react": {
1558
+ "version": "18.3.1",
1559
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
1560
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
1561
+ "license": "MIT",
1562
+ "dependencies": {
1563
+ "loose-envify": "^1.1.0"
1564
+ },
1565
+ "engines": {
1566
+ "node": ">=0.10.0"
1567
+ }
1568
+ },
1569
+ "node_modules/react-dom": {
1570
+ "version": "18.3.1",
1571
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
1572
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
1573
+ "license": "MIT",
1574
+ "dependencies": {
1575
+ "loose-envify": "^1.1.0",
1576
+ "scheduler": "^0.23.2"
1577
+ },
1578
+ "peerDependencies": {
1579
+ "react": "^18.3.1"
1580
+ }
1581
+ },
1582
+ "node_modules/react-refresh": {
1583
+ "version": "0.17.0",
1584
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
1585
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
1586
+ "dev": true,
1587
+ "license": "MIT",
1588
+ "engines": {
1589
+ "node": ">=0.10.0"
1590
+ }
1591
+ },
1592
+ "node_modules/rollup": {
1593
+ "version": "4.62.2",
1594
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
1595
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
1596
+ "dev": true,
1597
+ "license": "MIT",
1598
+ "dependencies": {
1599
+ "@types/estree": "1.0.9"
1600
+ },
1601
+ "bin": {
1602
+ "rollup": "dist/bin/rollup"
1603
+ },
1604
+ "engines": {
1605
+ "node": ">=18.0.0",
1606
+ "npm": ">=8.0.0"
1607
+ },
1608
+ "optionalDependencies": {
1609
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
1610
+ "@rollup/rollup-android-arm64": "4.62.2",
1611
+ "@rollup/rollup-darwin-arm64": "4.62.2",
1612
+ "@rollup/rollup-darwin-x64": "4.62.2",
1613
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
1614
+ "@rollup/rollup-freebsd-x64": "4.62.2",
1615
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
1616
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
1617
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
1618
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
1619
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
1620
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
1621
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
1622
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
1623
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
1624
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
1625
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
1626
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
1627
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
1628
+ "@rollup/rollup-openbsd-x64": "4.62.2",
1629
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
1630
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
1631
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
1632
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
1633
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
1634
+ "fsevents": "~2.3.2"
1635
+ }
1636
+ },
1637
+ "node_modules/scheduler": {
1638
+ "version": "0.23.2",
1639
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
1640
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
1641
+ "license": "MIT",
1642
+ "dependencies": {
1643
+ "loose-envify": "^1.1.0"
1644
+ }
1645
+ },
1646
+ "node_modules/semver": {
1647
+ "version": "6.3.1",
1648
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
1649
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
1650
+ "dev": true,
1651
+ "license": "ISC",
1652
+ "bin": {
1653
+ "semver": "bin/semver.js"
1654
+ }
1655
+ },
1656
+ "node_modules/source-map-js": {
1657
+ "version": "1.2.1",
1658
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1659
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1660
+ "dev": true,
1661
+ "license": "BSD-3-Clause",
1662
+ "engines": {
1663
+ "node": ">=0.10.0"
1664
+ }
1665
+ },
1666
+ "node_modules/tinyglobby": {
1667
+ "version": "0.2.17",
1668
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
1669
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
1670
+ "dev": true,
1671
+ "license": "MIT",
1672
+ "dependencies": {
1673
+ "fdir": "^6.5.0",
1674
+ "picomatch": "^4.0.4"
1675
+ },
1676
+ "engines": {
1677
+ "node": ">=12.0.0"
1678
+ },
1679
+ "funding": {
1680
+ "url": "https://github.com/sponsors/SuperchupuDev"
1681
+ }
1682
+ },
1683
+ "node_modules/update-browserslist-db": {
1684
+ "version": "1.2.3",
1685
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
1686
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
1687
+ "dev": true,
1688
+ "funding": [
1689
+ {
1690
+ "type": "opencollective",
1691
+ "url": "https://opencollective.com/browserslist"
1692
+ },
1693
+ {
1694
+ "type": "tidelift",
1695
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1696
+ },
1697
+ {
1698
+ "type": "github",
1699
+ "url": "https://github.com/sponsors/ai"
1700
+ }
1701
+ ],
1702
+ "license": "MIT",
1703
+ "dependencies": {
1704
+ "escalade": "^3.2.0",
1705
+ "picocolors": "^1.1.1"
1706
+ },
1707
+ "bin": {
1708
+ "update-browserslist-db": "cli.js"
1709
+ },
1710
+ "peerDependencies": {
1711
+ "browserslist": ">= 4.21.0"
1712
+ }
1713
+ },
1714
+ "node_modules/vite": {
1715
+ "version": "6.4.3",
1716
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
1717
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
1718
+ "dev": true,
1719
+ "license": "MIT",
1720
+ "dependencies": {
1721
+ "esbuild": "^0.25.0",
1722
+ "fdir": "^6.4.4",
1723
+ "picomatch": "^4.0.2",
1724
+ "postcss": "^8.5.3",
1725
+ "rollup": "^4.34.9",
1726
+ "tinyglobby": "^0.2.13"
1727
+ },
1728
+ "bin": {
1729
+ "vite": "bin/vite.js"
1730
+ },
1731
+ "engines": {
1732
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1733
+ },
1734
+ "funding": {
1735
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1736
+ },
1737
+ "optionalDependencies": {
1738
+ "fsevents": "~2.3.3"
1739
+ },
1740
+ "peerDependencies": {
1741
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1742
+ "jiti": ">=1.21.0",
1743
+ "less": "*",
1744
+ "lightningcss": "^1.21.0",
1745
+ "sass": "*",
1746
+ "sass-embedded": "*",
1747
+ "stylus": "*",
1748
+ "sugarss": "*",
1749
+ "terser": "^5.16.0",
1750
+ "tsx": "^4.8.1",
1751
+ "yaml": "^2.4.2"
1752
+ },
1753
+ "peerDependenciesMeta": {
1754
+ "@types/node": {
1755
+ "optional": true
1756
+ },
1757
+ "jiti": {
1758
+ "optional": true
1759
+ },
1760
+ "less": {
1761
+ "optional": true
1762
+ },
1763
+ "lightningcss": {
1764
+ "optional": true
1765
+ },
1766
+ "sass": {
1767
+ "optional": true
1768
+ },
1769
+ "sass-embedded": {
1770
+ "optional": true
1771
+ },
1772
+ "stylus": {
1773
+ "optional": true
1774
+ },
1775
+ "sugarss": {
1776
+ "optional": true
1777
+ },
1778
+ "terser": {
1779
+ "optional": true
1780
+ },
1781
+ "tsx": {
1782
+ "optional": true
1783
+ },
1784
+ "yaml": {
1785
+ "optional": true
1786
+ }
1787
+ }
1788
+ },
1789
+ "node_modules/yallist": {
1790
+ "version": "3.1.1",
1791
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
1792
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
1793
+ "dev": true,
1794
+ "license": "ISC"
1795
+ }
1796
+ }
1797
+ }
frontend/package.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "newsapp-frontend",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^18.3.1",
13
+ "react-dom": "^18.3.1"
14
+ },
15
+ "devDependencies": {
16
+ "@vitejs/plugin-react": "^4.3.4",
17
+ "vite": "^6.0.0"
18
+ }
19
+ }
frontend/src/App.css ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
2
+ body {
3
+ font-family: 'IBM Plex Serif', Georgia, 'Times New Roman', serif;
4
+ background: #000;
5
+ color: #a0a0a0;
6
+ min-height: 100vh;
7
+ overflow-x: hidden;
8
+ }
9
+ a { color: #d72924; text-decoration: none; }
10
+ a:hover, a:focus, a:active { color: #df5450; }
11
+ h1, h2, h3, h4, h5, h6 { font-family: Lora, Cambria, sans-serif; color: #fff; }
12
+ .header {
13
+ background: linear-gradient(0deg, rgba(0,0,0,0.92) 0%, rgba(215,41,36,0.92) 100%);
14
+ padding: 20px 32px;
15
+ position: sticky;
16
+ top: 0;
17
+ z-index: 50;
18
+ }
19
+ .header-inner {
20
+ max-width: 1400px;
21
+ margin: 0 auto;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: space-between;
25
+ flex-wrap: wrap;
26
+ gap: 12px;
27
+ }
28
+ .header h1 {
29
+ font-size: 22px;
30
+ font-weight: 700;
31
+ font-family: Lora, Cambria, sans-serif;
32
+ color: #fff;
33
+ }
34
+ .header span { font-size: 13px; color: #c0c0c0; }
35
+ .status { color: #c0c0c0; font-size: 13px; display: flex; align-items: center; gap: 8px; }
36
+ .status-dot {
37
+ width: 8px; height: 8px; border-radius: 50%;
38
+ background: #22c55e; display: inline-block;
39
+ animation: pulse-dot 2s ease-in-out infinite;
40
+ }
41
+ @keyframes pulse-dot {
42
+ 0%, 100% { opacity: 1; transform: scale(1); box-shadow: 0 0 0 0 rgba(34,197,94,0.6); }
43
+ 50% { opacity: 0.7; transform: scale(0.85); box-shadow: 0 0 0 6px rgba(34,197,94,0); }
44
+ }
45
+ .container { max-width: 1400px; margin: 0 auto; padding: 20px 32px; }
46
+
47
+ .tabs {
48
+ display: flex; gap: 4px; margin-bottom: 24px;
49
+ border-bottom: 1px solid #333; overflow-x: auto; position: relative;
50
+ }
51
+ .tab {
52
+ padding: 10px 20px; border: none; background: none; color: #666;
53
+ font-family: 'IBM Plex Serif', Georgia, serif; font-size: 14px;
54
+ font-weight: 600; cursor: pointer; white-space: nowrap; position: relative;
55
+ transition: color 0.25s ease;
56
+ }
57
+ .tab:hover { color: #c0c0c0; }
58
+ .tab.active { color: #d72924; }
59
+ .tab-count { font-size: 11px; color: #555; margin-left: 4px; transition: color 0.25s; }
60
+ .tab.active .tab-count { color: rgba(215,41,36,0.7); }
61
+ .tab-underline {
62
+ position: absolute; bottom: -1px; height: 2px; background: #d72924;
63
+ transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
64
+ box-shadow: 0 0 8px rgba(215,41,36,0.5);
65
+ }
66
+
67
+ .card {
68
+ background: rgba(12, 12, 12, 0.92);
69
+ border-radius: 0; overflow: hidden; border: 1px solid #222;
70
+ display: flex; flex-direction: column;
71
+ box-shadow: 1px 2px 3px rgba(255,255,255,.1);
72
+ opacity: 0; transform: translateY(15px);
73
+ transition: transform 0.2s ease-out, box-shadow 0.2s ease-out, border-color 0.2s ease-out, opacity 0.3s ease-out;
74
+ }
75
+ .card.revealed { opacity: 1; transform: translateY(0); }
76
+ .card:hover {
77
+ transform: translateY(-3px) scale(1.02);
78
+ box-shadow: 0 0 20px rgba(215,41,36,0.15), 0 8px 24px rgba(255,255,255,.1);
79
+ border-color: rgba(215,41,36,0.3);
80
+ }
81
+ .card-image-wrap { overflow: hidden; height: 180px; }
82
+ .card-image {
83
+ width: 100%; height: 180px; object-fit: cover;
84
+ background: #111; display: block; transition: transform 0.3s ease-out;
85
+ }
86
+ .card:hover .card-image { transform: scale(1.05); }
87
+ .card-image-placeholder {
88
+ width: 100%; height: 180px;
89
+ background: linear-gradient(135deg, #111, #000);
90
+ display: flex; align-items: center; justify-content: center;
91
+ font-size: 40px; color: #333;
92
+ }
93
+ .card-body { padding: 14px 18px 18px; flex: 1; display: flex; flex-direction: column; }
94
+ .card-rank {
95
+ font-size: 10px; font-weight: 700; color: #666;
96
+ letter-spacing: .05em; margin-bottom: 4px;
97
+ }
98
+ .card-topic {
99
+ font-size: 10px; font-weight: 600; text-transform: uppercase;
100
+ letter-spacing: .08em; color: #d72924; margin-bottom: 6px;
101
+ }
102
+ .card-title {
103
+ font-size: 15px; font-weight: 600; font-family: Lora, Cambria, sans-serif;
104
+ line-height: 1.4; margin-bottom: 6px; color: #fff;
105
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
106
+ }
107
+ .card-summary {
108
+ font-size: 13px; color: #a0a0a0; line-height: 1.5; margin-bottom: 10px;
109
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
110
+ flex: 1;
111
+ }
112
+ .card-meta { display: flex; align-items: center; gap: 10px; font-size: 12px; color: #c0c0c0; margin-bottom: 10px; flex-wrap: wrap; }
113
+ .card-meta .source { color: #c0c0c0; }
114
+ .sponsor { font-size: 10px; color: #fbbf24; background: rgba(251, 191, 36, 0.1); padding: 2px 6px; border-radius: 4px; }
115
+ .bias-badge { font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; letter-spacing: .03em; }
116
+ .bias-left { background: rgba(59,130,246,0.15); color: #60a5fa; }
117
+ .bias-left-center { background: rgba(99,102,241,0.15); color: #818cf8; }
118
+ .bias-center { background: rgba(148,163,184,0.15); color: #94a3b8; }
119
+ .bias-right-center { background: rgba(239,68,68,0.15); color: #f87171; }
120
+ .bias-right { background: rgba(220,38,38,0.15); color: #ef4444; }
121
+ .bias-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
122
+ .factuality-badge { font-size: 10px; font-weight: 600; padding: 2px 6px; border-radius: 4px; letter-spacing: .03em; }
123
+ .factuality-high { background: rgba(34,197,94,0.15); color: #4ade80; }
124
+ .factuality-mixed { background: rgba(251,191,36,0.15); color: #fbbf24; }
125
+ .factuality-low { background: rgba(239,68,68,0.15); color: #f87171; }
126
+ .factuality-satire { background: rgba(168,85,247,0.15); color: #c084fc; }
127
+ .category-badge { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em; padding: 3px 8px; }
128
+ .cat-government { background: rgba(239,68,68,0.15); color: #f87171; }
129
+ .cat-conglomerate { background: rgba(139,92,246,0.15); color: #a78bfa; }
130
+ .cat-private_equity { background: rgba(251,191,36,0.15); color: #fbbf24; }
131
+ .cat-wealthy_private { background: rgba(236,72,153,0.15); color: #f472b6; }
132
+ .cat-corporate { background: rgba(59,130,246,0.15); color: #60a5fa; }
133
+ .cat-independent { background: rgba(34,197,94,0.15); color: #4ade80; }
134
+ .score-bar-wrap { height: 4px; background: #222; border-radius: 2px; margin-bottom: 3px; overflow: hidden; }
135
+ .score-bar-fill {
136
+ height: 100%; border-radius: 2px;
137
+ background: linear-gradient(90deg, #d72924, #df5450);
138
+ width: 0%; transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
139
+ }
140
+ .score-label { font-size: 10px; color: #666; display: flex; justify-content: space-between; }
141
+ .card-actions { display: flex; gap: 6px; margin-top: 6px; }
142
+ .btn {
143
+ display: inline-flex; align-items: center; gap: 4px;
144
+ padding: 6px 14px; border-radius: 4px; font-size: 12px;
145
+ font-weight: 600; font-family: 'IBM Plex Serif', Georgia, serif;
146
+ text-decoration: none; cursor: pointer; border: none;
147
+ transition: transform 0.15s ease, background 0.2s ease, filter 0.2s ease;
148
+ }
149
+ .btn:hover { filter: brightness(1.2); transform: scale(1.04); }
150
+ .btn:active { transform: scale(0.97) !important; }
151
+ .btn-primary {
152
+ background: linear-gradient(to right, #d72924 0%, #db3e3a 51%, #d72924 100%);
153
+ color: #fff;
154
+ }
155
+ .btn-secondary { background: #222; color: #c0c0c0; }
156
+ .btn-secondary:hover { background: #333; filter: brightness(1.2); color: #fff; }
157
+ .card-date {
158
+ margin-left: auto; font-size: 12px; font-weight: 700; color: #d72924;
159
+ background: rgba(215, 41, 36, 0.08); padding: 3px 10px;
160
+ border: 1px solid rgba(215, 41, 36, 0.2); transition: background 0.2s, border-color 0.2s;
161
+ }
162
+ .card:hover .card-date { background: rgba(215,41,36,0.15); border-color: rgba(215,41,36,0.4); }
163
+ .card-topics { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
164
+ .topic-tag { font-size: 10px; padding: 2px 7px; background: #222; color: #c0c0c0; }
165
+
166
+ .date-group { margin-bottom: 16px; }
167
+ .date-header {
168
+ font-size: 12px; font-weight: 700; color: #d72924; text-transform: uppercase;
169
+ letter-spacing: .08em; margin-bottom: 10px; padding-bottom: 6px; border-bottom: 1px solid #333;
170
+ }
171
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 16px; }
172
+
173
+ .empty { text-align: center; padding: 60px 20px; color: #666; }
174
+ .empty h2 { font-size: 18px; margin-bottom: 6px; color: #fff; }
175
+
176
+ @keyframes shimmer {
177
+ 0% { background-position: -200px 0; }
178
+ 100% { background-position: calc(200px + 100%) 0; }
179
+ }
180
+ .skeleton {
181
+ background: linear-gradient(90deg, #1a1a1a 25%, #2a2a2a 50%, #1a1a1a 75%);
182
+ background-size: 200px 100%; animation: shimmer 1.5s ease-in-out infinite; border-radius: 2px;
183
+ }
184
+ .skeleton-img { height: 180px; width: 100%; }
185
+ .skeleton-text { height: 14px; margin-bottom: 8px; width: 80%; }
186
+ .skeleton-text-short { height: 14px; margin-bottom: 8px; width: 50%; }
187
+
188
+ .modal-overlay {
189
+ display: none; position: fixed; inset: 0;
190
+ background: rgba(0,0,0,0.85); z-index: 1000;
191
+ justify-content: center; align-items: center; padding: 20px;
192
+ opacity: 0; transition: opacity 0.25s ease;
193
+ }
194
+ .modal-overlay.active { display: flex; opacity: 1; }
195
+ .modal {
196
+ background: rgba(12,12,12,0.96); border: 1px solid #333;
197
+ max-width: 480px; width: 100%; padding: 28px; position: relative;
198
+ box-shadow: 0 0 40px rgba(215,41,36,0.15);
199
+ transform: translateY(20px) scale(0.95);
200
+ transition: transform 0.25s ease, opacity 0.25s ease;
201
+ opacity: 0;
202
+ }
203
+ .modal-overlay.active .modal { transform: translateY(0) scale(1); opacity: 1; }
204
+ .modal-close {
205
+ position: absolute; top: 12px; right: 16px;
206
+ background: none; border: none; color: #666;
207
+ font-size: 22px; cursor: pointer;
208
+ font-family: 'IBM Plex Serif', Georgia, serif;
209
+ transition: color 0.2s, transform 0.15s;
210
+ }
211
+ .modal-close:hover { color: #d72924; transform: rotate(90deg); }
212
+ .modal h2 { font-size: 18px; color: #d72924; margin-bottom: 4px; }
213
+ .modal .parent { font-size: 13px; color: #c0c0c0; margin-bottom: 12px; }
214
+ .modal-row { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
215
+ .modal-section { margin-bottom: 14px; }
216
+ .modal-section h3 { font-size: 11px; font-weight: 600; color: #666; text-transform: uppercase; letter-spacing: .06em; margin-bottom: 6px; }
217
+ .owner-list { display: flex; flex-wrap: wrap; gap: 6px; }
218
+ .owner-tag { font-size: 12px; padding: 4px 10px; background: rgba(215,41,36,0.08); color: #d72924; border: 1px solid rgba(215,41,36,0.2); }
219
+ .owner-wiki { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; font-size: 14px; background: rgba(215,41,36,0.08); color: #d72924; border: 1px solid rgba(215,41,36,0.2); text-decoration: none; transition: background 0.2s, transform 0.15s; }
220
+ .owner-wiki:hover { background: rgba(215,41,36,0.25); color: #df5450; transform: scale(1.1); }
221
+ .modal-wiki {
222
+ display: inline-block; margin-top: 6px; padding: 8px 16px;
223
+ background: linear-gradient(to right, #d72924 0%, #db3e3a 51%, #d72924 100%);
224
+ color: #fff; text-decoration: none; font-size: 13px;
225
+ font-weight: 600; font-family: 'IBM Plex Serif', Georgia, serif;
226
+ transition: opacity 0.2s, transform 0.15s;
227
+ }
228
+ .modal-wiki:hover { opacity: .85; color: #fff; transform: scale(1.03); }
229
+
230
+ @media (max-width: 640px) {
231
+ .header { padding: 14px; }
232
+ .container { padding: 14px; }
233
+ .grid { grid-template-columns: 1fr; }
234
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,358 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useEffect, useRef, useCallback } from 'react'
2
+ import { fetchCategories, fetchPosts, fetchStatus } from './api'
3
+
4
+ const CAT_LABELS = {
5
+ government: '\u{1F3DB}\uFE0F Government',
6
+ conglomerate: '\u{1F3E2} Media Conglomerate',
7
+ private_equity: '\u{1F4B0} Private Equity',
8
+ wealthy_private: '\u{1F464} Wealthy Private Owner',
9
+ corporate: '\u{1F4CA} Corporate',
10
+ independent: '\u2705 Independent',
11
+ }
12
+
13
+ function groupByDate(clusters) {
14
+ const map = {}
15
+ for (const c of clusters) {
16
+ const d = c.articles[0]?.published || 'Unknown'
17
+ if (!map[d]) map[d] = []
18
+ map[d].push(c)
19
+ }
20
+ const sorted = Object.entries(map).sort((a, b) => {
21
+ if (a[0] === 'Unknown') return 1
22
+ if (b[0] === 'Unknown') return -1
23
+ return b[0].localeCompare(a[0])
24
+ })
25
+ return sorted.map(([date, items]) => ({ date, items }))
26
+ }
27
+
28
+ function SafeImage({ src, className, wrapClass }) {
29
+ const [failed, setFailed] = useState(false)
30
+ if (!src || failed) {
31
+ return <div className="card-image-placeholder">{'\u{1F4F0}'}</div>
32
+ }
33
+ return (
34
+ <div className={wrapClass}>
35
+ <img className={className} src={src} alt="" loading="lazy" onError={() => setFailed(true)} />
36
+ </div>
37
+ )
38
+ }
39
+
40
+ function ScoreBar({ score }) {
41
+ const barRef = useRef(null)
42
+ const animated = useRef(false)
43
+
44
+ useEffect(() => {
45
+ if (animated.current) return
46
+ const el = barRef.current
47
+ if (!el) return
48
+
49
+ const reveal = () => {
50
+ if (animated.current) return
51
+ animated.current = true
52
+ const target = score * 100
53
+ el.style.width = '0%'
54
+ requestAnimationFrame(() => {
55
+ requestAnimationFrame(() => {
56
+ el.style.width = target + '%'
57
+ })
58
+ })
59
+ }
60
+
61
+ const parent = el.closest('.card')
62
+ if (parent && parent.classList.contains('revealed')) {
63
+ reveal()
64
+ } else {
65
+ const obs = new MutationObserver(() => {
66
+ if (parent && parent.classList.contains('revealed')) {
67
+ reveal()
68
+ obs.disconnect()
69
+ }
70
+ })
71
+ if (parent) obs.observe(parent, { attributes: true, attributeFilter: ['class'] })
72
+ return () => obs.disconnect()
73
+ }
74
+ }, [score])
75
+
76
+ return (
77
+ <div className="score-bar-wrap">
78
+ <div className="score-bar-fill" ref={barRef}></div>
79
+ </div>
80
+ )
81
+ }
82
+
83
+ export default function App() {
84
+ const [categories, setCategories] = useState([])
85
+ const [allClusters, setAllClusters] = useState([])
86
+ const [loading, setLoading] = useState(true)
87
+ const [activeTab, setActiveTab] = useState(0)
88
+ const [status, setStatus] = useState('starting')
89
+ const [total, setTotal] = useState(0)
90
+ const [sponsorInfo, setSponsorInfo] = useState(null)
91
+ const tabsRef = useRef(null)
92
+ const underlineRef = useRef(null)
93
+ const observerRef = useRef(null)
94
+
95
+ useEffect(() => {
96
+ Promise.all([fetchCategories(), fetchPosts(), fetchStatus()])
97
+ .then(([catRes, postRes, statusRes]) => {
98
+ setCategories(catRes.categories)
99
+ setAllClusters(postRes.clusters)
100
+ setTotal(postRes.meta.total)
101
+ setStatus(statusRes.pipeline_status)
102
+ })
103
+ .catch(() => {})
104
+ .finally(() => setLoading(false))
105
+ }, [])
106
+
107
+ useEffect(() => {
108
+ if (!underlineRef.current || !tabsRef.current) return
109
+ const tab = tabsRef.current.children[activeTab]
110
+ if (!tab) return
111
+ underlineRef.current.style.left = tab.offsetLeft + 'px'
112
+ underlineRef.current.style.width = tab.offsetWidth + 'px'
113
+ }, [activeTab, categories])
114
+
115
+ useEffect(() => {
116
+ observerRef.current = new IntersectionObserver((entries) => {
117
+ for (const entry of entries) {
118
+ if (entry.isIntersecting) {
119
+ entry.target.classList.add('revealed')
120
+ observerRef.current.unobserve(entry.target)
121
+ }
122
+ }
123
+ }, { threshold: 0.1, rootMargin: '0px 0px 40px 0px' })
124
+ return () => observerRef.current?.disconnect()
125
+ }, [])
126
+
127
+ useEffect(() => {
128
+ if (loading) return
129
+ const timer = setTimeout(() => {
130
+ const tabKey = categories[activeTab]
131
+ if (!tabKey) return
132
+ const tc = document.getElementById('tab-' + tabKey.replace(/[\s/]+/g, '_'))
133
+ if (!tc) return
134
+ tc.querySelectorAll('.card:not(.revealed)').forEach(c => {
135
+ observerRef.current?.observe(c)
136
+ })
137
+ }, 50)
138
+ return () => clearTimeout(timer)
139
+ }, [activeTab, loading, categories])
140
+
141
+ useEffect(() => {
142
+ if (loading || total === 0) return
143
+ const el = document.getElementById('count-total')
144
+ if (!el) return
145
+ let current = 0
146
+ const step = Math.max(1, Math.floor(total / 20))
147
+ const interval = setInterval(() => {
148
+ current += step
149
+ if (current >= total) {
150
+ current = total
151
+ clearInterval(interval)
152
+ }
153
+ el.textContent = current
154
+ }, 40)
155
+ return () => clearInterval(interval)
156
+ }, [loading, total])
157
+
158
+ useEffect(() => {
159
+ const handleKey = (e) => {
160
+ if (e.key === 'Escape') setSponsorInfo(null)
161
+ }
162
+ document.addEventListener('keydown', handleKey)
163
+ return () => document.removeEventListener('keydown', handleKey)
164
+ }, [])
165
+
166
+ const tabId = (name) => name.replace(/[\s/]+/g, '_')
167
+ const tabKey = categories[activeTab]
168
+ const filtered = tabKey ? allClusters.filter(c => c.category === tabKey) : []
169
+ const dateGroups = groupByDate(filtered)
170
+
171
+ return (
172
+ <>
173
+ <div className="header">
174
+ <div className="header-inner">
175
+ <div>
176
+ <h1>News Digest</h1>
177
+ <span>
178
+ <span id="count-total" data-target={total}>0</span>
179
+ {' stories across '}{categories.length}{' topics'}
180
+ </span>
181
+ </div>
182
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
183
+ <span className="status">
184
+ <span className="status-dot"></span>
185
+ {status}
186
+ </span>
187
+ </div>
188
+ </div>
189
+ </div>
190
+
191
+ <div className="container">
192
+ {loading ? (
193
+ <div className="grid">
194
+ {Array.from({ length: 6 }).map((_, i) => (
195
+ <div className="card" key={i} style={{ opacity: 1, transform: 'none', borderColor: '#1a1a1a' }}>
196
+ <div className="skeleton skeleton-img"></div>
197
+ <div className="card-body">
198
+ <div className="skeleton skeleton-text-short"></div>
199
+ <div className="skeleton skeleton-text"></div>
200
+ <div className="skeleton skeleton-text" style={{ width: '60%' }}></div>
201
+ <div style={{ marginTop: 'auto' }}>
202
+ <div className="skeleton" style={{ height: 4, marginBottom: 3 }}></div>
203
+ <div className="skeleton skeleton-text-short" style={{ marginBottom: 0 }}></div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ ))}
208
+ </div>
209
+ ) : total === 0 ? (
210
+ <div className="empty">
211
+ <h2>No news yet</h2>
212
+ <p>Run <code>python webapp.py</code> to fetch and analyze articles</p>
213
+ </div>
214
+ ) : (
215
+ <>
216
+ <div className="tabs" ref={tabsRef}>
217
+ {categories.map((cat, i) => (
218
+ <button
219
+ key={cat}
220
+ className={'tab' + (i === activeTab ? ' active' : '')}
221
+ onClick={() => setActiveTab(i)}
222
+ >
223
+ {cat}{' '}
224
+ <span className="tab-count">
225
+ ({allClusters.filter(c => c.category === cat).length})
226
+ </span>
227
+ </button>
228
+ ))}
229
+ <div className="tab-underline" ref={underlineRef}></div>
230
+ </div>
231
+
232
+ {categories.map((cat, i) => (
233
+ <div
234
+ key={cat}
235
+ id={'tab-' + tabId(cat)}
236
+ style={{ display: i === activeTab ? 'block' : 'none' }}
237
+ >
238
+ {dateGroups.length === 0 ? (
239
+ <div className="empty"><h2>No {cat} stories found</h2></div>
240
+ ) : (
241
+ dateGroups.map(group => (
242
+ <div className="date-group" key={group.date}>
243
+ <div className="date-header">{group.date}</div>
244
+ <div className="grid">
245
+ {group.items.map((c, idx) => {
246
+ const a = c.articles[0]
247
+ return (
248
+ <div className="card" key={c.top_post_url || idx}>
249
+ <SafeImage
250
+ src={c.image_url}
251
+ className="card-image"
252
+ wrapClass="card-image-wrap"
253
+ />
254
+ <div className="card-body">
255
+ <div className="card-rank">#{idx + 1}</div>
256
+ <div className="card-topic">{c.topic}</div>
257
+ <div className="card-title">{a.title}</div>
258
+ {a.summary && (
259
+ <div className="card-summary">
260
+ {a.summary.length > 160 ? a.summary.slice(0, 160) + '\u2026' : a.summary}
261
+ </div>
262
+ )}
263
+ <div className="card-topics">
264
+ {(a.topics || []).slice(0, 3).map(t => (
265
+ <span className="topic-tag" key={t}>{t}</span>
266
+ ))}
267
+ </div>
268
+ <div className="card-meta">
269
+ <span className="source">{a.domain}</span>
270
+ <span>{'\u{1F6E1}\uFE0F'} {c.trust}</span>
271
+ {a.article_leaning && (
272
+ <span className={'bias-badge bias-' + a.article_leaning}>{a.article_leaning}</span>
273
+ )}
274
+ {a.source_bias && a.source_bias !== a.article_leaning && (
275
+ <span style={{ fontSize: 9, color: '#64748b' }}>src:{a.source_bias}</span>
276
+ )}
277
+ {a.source_factuality && (
278
+ <span className={'factuality-badge factuality-' + a.source_factuality}>{a.source_factuality}</span>
279
+ )}
280
+ {c.coverage > 1 && <span>{'\u{1F4F0}'} {c.coverage}</span>}
281
+ {a.sponsor && <span className="sponsor">{'\u{1F3E2}'} {a.sponsor}</span>}
282
+ </div>
283
+ <ScoreBar score={c.score} />
284
+ <div className="score-label">
285
+ <span>Relevance</span>
286
+ <span>{c.score}</span>
287
+ </div>
288
+ <div className="card-actions">
289
+ <button className="btn btn-primary" onClick={() => setSponsorInfo(a.sponsor_info)}>
290
+ {'\u{1F3E2}'} Sponsors
291
+ </button>
292
+ <a href={c.top_post_url} className="btn btn-secondary" target="_blank">Open</a>
293
+ {a.published && <span className="card-date">{a.published}</span>}
294
+ </div>
295
+ </div>
296
+ </div>
297
+ )
298
+ })}
299
+ </div>
300
+ </div>
301
+ ))
302
+ )}
303
+ </div>
304
+ ))}
305
+ </>
306
+ )}
307
+ </div>
308
+
309
+ <div
310
+ className={'modal-overlay' + (sponsorInfo ? ' active' : '')}
311
+ onClick={(e) => { if (e.target === e.currentTarget) setSponsorInfo(null) }}
312
+ >
313
+ <div className="modal">
314
+ <button className="modal-close" onClick={() => setSponsorInfo(null)}>{'\u2715'}</button>
315
+ {sponsorInfo ? (
316
+ <>
317
+ <h2>{sponsorInfo.display}</h2>
318
+ <div className="parent">Parent: {sponsorInfo.parent || '\u2014'}</div>
319
+ <div className="modal-row">
320
+ {sponsorInfo.category && (
321
+ <span className={'category-badge cat-' + sponsorInfo.category}>
322
+ {CAT_LABELS[sponsorInfo.category] || sponsorInfo.category}
323
+ </span>
324
+ )}
325
+ {sponsorInfo.bias && (
326
+ <span className={'bias-badge bias-' + sponsorInfo.bias}>{sponsorInfo.bias}</span>
327
+ )}
328
+ {sponsorInfo.factuality && (
329
+ <span className={'factuality-badge factuality-' + sponsorInfo.factuality}>{sponsorInfo.factuality}</span>
330
+ )}
331
+ </div>
332
+ {sponsorInfo.owners && sponsorInfo.owners.length > 0 && (
333
+ <div className="modal-section">
334
+ <h3>Shareholders / Funders</h3>
335
+ <div className="owner-list">
336
+ {sponsorInfo.owners.map(o => (
337
+ <span key={o} style={{ display: 'inline-flex', gap: 4, alignItems: 'center' }}>
338
+ <span className="owner-tag">{o}</span>
339
+ {sponsorInfo.owner_wikis && sponsorInfo.owner_wikis[o] && (
340
+ <a href={sponsorInfo.owner_wikis[o]} className="owner-wiki" target="_blank" title="Wikipedia">{'\u{1F4D6}'}</a>
341
+ )}
342
+ </span>
343
+ ))}
344
+ </div>
345
+ </div>
346
+ )}
347
+ {sponsorInfo.wikipedia && (
348
+ <a href={sponsorInfo.wikipedia} className="modal-wiki" target="_blank">{'\u{1F4D6}'} Wikipedia</a>
349
+ )}
350
+ </>
351
+ ) : (
352
+ <p style={{ color: '#666' }}>No sponsor data available.</p>
353
+ )}
354
+ </div>
355
+ </div>
356
+ </>
357
+ )
358
+ }
frontend/src/api.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const API_BASE = '/api/v1'
2
+
3
+ export async function fetchStatus() {
4
+ const r = await fetch(`${API_BASE}/status`)
5
+ return r.json()
6
+ }
7
+
8
+ export async function fetchPosts(params = {}) {
9
+ const q = new URLSearchParams()
10
+ if (params.category) q.set('category', params.category)
11
+ if (params.topic) q.set('topic', params.topic)
12
+ if (params.limit) q.set('limit', params.limit)
13
+ if (params.min_score) q.set('min_score', params.min_score)
14
+ const r = await fetch(`${API_BASE}/posts?${q}`)
15
+ return r.json()
16
+ }
17
+
18
+ export async function fetchCategories() {
19
+ const r = await fetch(`${API_BASE}/categories`)
20
+ return r.json()
21
+ }
frontend/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App'
4
+ import './App.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
frontend/vite.config.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ base: '/',
7
+ build: {
8
+ outDir: 'dist',
9
+ },
10
+ })
webapp.py CHANGED
@@ -6,7 +6,7 @@ import threading
6
  from collections import defaultdict
7
  from datetime import datetime, timezone
8
 
9
- from flask import Flask, jsonify, render_template, request
10
  from flask_cors import CORS
11
 
12
  from config import Config
@@ -21,7 +21,6 @@ logger = logging.getLogger(__name__)
21
  cfg = Config()
22
 
23
  app = Flask(__name__)
24
- app.config["TEMPLATES_AUTO_RELOAD"] = True
25
  CORS(app, origins=cfg.cors_origins.split(",") if cfg.cors_origins != "*" else "*")
26
 
27
  cache_lock = threading.Lock()
@@ -164,30 +163,17 @@ def _load_cache():
164
  return None
165
 
166
 
167
- # ---- Web UI ----
 
 
168
 
169
  @app.route("/")
170
  def index():
171
- with cache_lock:
172
- bc = cached_by_cat
173
- status = cached_status
174
- grouped_by_date = {}
175
- for cat in CATEGORIES:
176
- items = bc.get(cat, [])
177
- items.sort(key=lambda x: (x["articles"][0]["published_iso"] or "", x["score"]), reverse=True)
178
- date_groups = []
179
- seen_date = None
180
- for item in items:
181
- d = item["articles"][0]["published"] or "Unknown"
182
- if d != seen_date:
183
- date_groups.append({"date": d, "items": []})
184
- seen_date = d
185
- date_groups[-1]["items"].append(item)
186
- grouped_by_date[cat] = date_groups
187
-
188
- total = sum(len(g["items"]) for groups in grouped_by_date.values() for g in groups)
189
- tab_counts = {cat: sum(len(g["items"]) for g in grouped_by_date[cat]) for cat in CATEGORIES}
190
- return render_template("index.html", grouped_by_date=grouped_by_date, categories=CATEGORIES, status=status, total=total, tab_counts=tab_counts)
191
 
192
 
193
  # ---- REST API ----
 
6
  from collections import defaultdict
7
  from datetime import datetime, timezone
8
 
9
+ from flask import Flask, jsonify, request, send_from_directory
10
  from flask_cors import CORS
11
 
12
  from config import Config
 
21
  cfg = Config()
22
 
23
  app = Flask(__name__)
 
24
  CORS(app, origins=cfg.cors_origins.split(",") if cfg.cors_origins != "*" else "*")
25
 
26
  cache_lock = threading.Lock()
 
163
  return None
164
 
165
 
166
+ # ---- Web UI (React SPA) ----
167
+
168
+ FRONTEND_DIST = os.path.join(os.path.dirname(__file__), "frontend", "dist")
169
 
170
  @app.route("/")
171
  def index():
172
+ return send_from_directory(FRONTEND_DIST, "index.html")
173
+
174
+ @app.route("/assets/<path:filename>")
175
+ def serve_assets(filename):
176
+ return send_from_directory(os.path.join(FRONTEND_DIST, "assets"), filename)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
 
179
  # ---- REST API ----