Prathamesh Bhamare commited on
Commit
77f1d1a
·
1 Parent(s): d496e2d

feat: wire frontend buttons to API + fix HF Spaces config

Browse files

- Wire all desktop.html buttons to FastAPI endpoints (predict, health, docs)
- Wire all mobile.html buttons with same API integration
- Add interactive terminal: editable input, POST /predict/f1, render results
- Add health indicator polling GET /health every 30s
- Add smooth-scroll navigation with section anchors
- Add toast notification system for success/error feedback
- Add mobile viewport detection in index.html
- Add HF Spaces YAML frontmatter to README.md (fixes config error)
- Update CORS to support file:// protocol for local dev
- Remove deleted ui/ folder references from Dockerfiles
- Remove Streamlit service from docker-compose and nginx
- Update drift report output path to data_output/

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. Dockerfile +0 -1
  2. Dockerfile.hf +0 -1
  3. README.md +12 -12
  4. Screenshot 2026-06-11 123347.png +3 -0
  5. api/main.py +1 -1
  6. docker-compose.yml +0 -26
  7. frontend/.gitignore +0 -41
  8. frontend/AGENTS.md +0 -5
  9. frontend/CLAUDE.md +0 -1
  10. frontend/README.md +0 -36
  11. frontend/app/favicon.ico +0 -0
  12. frontend/app/globals.css +0 -76
  13. frontend/app/layout.js +0 -14
  14. frontend/app/page.js +0 -33
  15. frontend/app/page.module.css +0 -365
  16. frontend/components/saas/DriverComparison.tsx +0 -40
  17. frontend/components/saas/Hero.tsx +0 -21
  18. frontend/components/saas/Metrics.tsx +0 -30
  19. frontend/components/saas/Navbar.tsx +0 -42
  20. frontend/components/saas/PrimaryActions.tsx +0 -93
  21. frontend/components/saas/ShapExplanation.tsx +0 -52
  22. frontend/components/saas/Telemetry.tsx +0 -46
  23. frontend/components/saas/UpcomingGP.tsx +0 -40
  24. frontend/components/ui/button.tsx +0 -52
  25. frontend/components/ui/popover.tsx +0 -40
  26. frontend/components/ui/ruixen-query-box.tsx +0 -80
  27. frontend/components/ui/textarea.tsx +0 -21
  28. frontend/desktop.html +550 -0
  29. frontend/eslint.config.mjs +0 -16
  30. frontend/hooks/use-auto-resize-textarea.ts +0 -57
  31. frontend/index.html +33 -0
  32. frontend/jsconfig.json +0 -7
  33. frontend/lib/utils.ts +0 -6
  34. frontend/mobile.html +541 -0
  35. frontend/next.config.mjs +0 -6
  36. frontend/package-lock.json +0 -0
  37. frontend/package.json +0 -32
  38. frontend/postcss.config.mjs +0 -7
  39. frontend/public/file.svg +0 -1
  40. frontend/public/globe.svg +0 -1
  41. frontend/public/metrics/confusion_matrix.svg +0 -984
  42. frontend/public/metrics/feature_importance.svg +0 -2058
  43. frontend/public/metrics/pr_curve.svg +0 -2038
  44. frontend/public/metrics/roc_curve.svg +0 -1497
  45. frontend/public/next.svg +0 -1
  46. frontend/public/vercel.svg +0 -1
  47. frontend/public/window.svg +0 -1
  48. frontend/tsconfig.json +0 -26
  49. ml/drift_detection.py +1 -1
  50. mlruns/models/kronector-f1-lgbm/version-1/meta.yaml +8 -0
Dockerfile CHANGED
@@ -29,7 +29,6 @@ COPY --chown=1000:1000 api/ ./api/
29
  COPY --chown=1000:1000 ml/ ./ml/
30
  COPY --chown=1000:1000 data/ ./data/
31
  COPY --chown=1000:1000 scripts/ ./scripts/
32
- COPY --chown=1000:1000 ui/ ./ui/
33
  COPY --chown=1000:1000 run_api.py run_all.py ./
34
 
35
  # Copy pre-trained model artifacts and data
 
29
  COPY --chown=1000:1000 ml/ ./ml/
30
  COPY --chown=1000:1000 data/ ./data/
31
  COPY --chown=1000:1000 scripts/ ./scripts/
 
32
  COPY --chown=1000:1000 run_api.py run_all.py ./
33
 
34
  # Copy pre-trained model artifacts and data
Dockerfile.hf CHANGED
@@ -30,7 +30,6 @@ COPY --chown=1000:1000 api/ ./api/
30
  COPY --chown=1000:1000 ml/ ./ml/
31
  COPY --chown=1000:1000 data/ ./data/
32
  COPY --chown=1000:1000 scripts/ ./scripts/
33
- COPY --chown=1000:1000 ui/ ./ui/
34
  COPY --chown=1000:1000 run_api.py run_all.py ./
35
 
36
  # Copy pre-trained model artifacts and data
 
30
  COPY --chown=1000:1000 ml/ ./ml/
31
  COPY --chown=1000:1000 data/ ./data/
32
  COPY --chown=1000:1000 scripts/ ./scripts/
 
33
  COPY --chown=1000:1000 run_api.py run_all.py ./
34
 
35
  # Copy pre-trained model artifacts and data
README.md CHANGED
@@ -1,3 +1,13 @@
 
 
 
 
 
 
 
 
 
 
1
  <div align="center">
2
 
3
  # Kronector: F1 Race Outcome Prediction & MLOps System
@@ -58,11 +68,7 @@ To prove the model captures complex non-linear relationships rather than just pr
58
  | Pole Position Wins | ~42% |
59
 
60
  ### Feature Importance & ROC Proofs
61
- *Mathematical proof that the model relies on logical racing factors.*
62
- <div align="center">
63
- <img src="frontend/public/metrics/feature_importance.svg" width="48%">
64
- <img src="frontend/public/metrics/roc_curve.svg" width="48%">
65
- </div>
66
 
67
  ---
68
 
@@ -87,7 +93,6 @@ graph TD
87
  subgraph Serving
88
  E --> G[FastAPI Backend]
89
  G --> H[Multi-Agent LLM]
90
- H --> I[React Frontend]
91
  end
92
  ```
93
 
@@ -125,17 +130,12 @@ python -m data.build_driver_map
125
  python -m scripts.auto_retrain_pipeline
126
  ```
127
 
128
- ### 3. Run the API & Frontend
129
  Create a `.env` file with `GROQ_API_KEY=your_key` and the `KRONECTOR_MODEL_RUN_ID` outputted by the training script.
130
 
131
  ```bash
132
  # Start backend
133
  python -m uvicorn api.main:app --reload
134
-
135
- # Start frontend (in a new terminal)
136
- cd frontend
137
- npm install
138
- npm run dev
139
  ```
140
 
141
  ---
 
1
+ ---
2
+ title: Kronector
3
+ emoji: 🏎️
4
+ colorFrom: red
5
+ colorTo: gray
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ ---
10
+
11
  <div align="center">
12
 
13
  # Kronector: F1 Race Outcome Prediction & MLOps System
 
68
  | Pole Position Wins | ~42% |
69
 
70
  ### Feature Importance & ROC Proofs
71
+ *Mathematical proof that the model relies on logical racing factors can be regenerated with `python -m scripts.generate_model_proofs`.*
 
 
 
 
72
 
73
  ---
74
 
 
93
  subgraph Serving
94
  E --> G[FastAPI Backend]
95
  G --> H[Multi-Agent LLM]
 
96
  end
97
  ```
98
 
 
130
  python -m scripts.auto_retrain_pipeline
131
  ```
132
 
133
+ ### 3. Run the API
134
  Create a `.env` file with `GROQ_API_KEY=your_key` and the `KRONECTOR_MODEL_RUN_ID` outputted by the training script.
135
 
136
  ```bash
137
  # Start backend
138
  python -m uvicorn api.main:app --reload
 
 
 
 
 
139
  ```
140
 
141
  ---
Screenshot 2026-06-11 123347.png ADDED

Git LFS Details

  • SHA256: 108b99a2815fe1f01140d1efa380ed05ee2cc5473191b635c023a1699a1fe53c
  • Pointer size: 131 Bytes
  • Size of remote file: 217 kB
api/main.py CHANGED
@@ -185,7 +185,7 @@ import os
185
 
186
  _cors_origins = os.getenv(
187
  "CORS_ORIGINS",
188
- "http://localhost:3000,http://localhost:8501,http://localhost:5173",
189
  ).split(",")
190
 
191
  app.add_middleware(
 
185
 
186
  _cors_origins = os.getenv(
187
  "CORS_ORIGINS",
188
+ "http://localhost:3000,http://localhost:8501,http://localhost:5173,http://localhost:8080,http://localhost:8000,null",
189
  ).split(",")
190
 
191
  app.add_middleware(
docker-compose.yml CHANGED
@@ -3,7 +3,6 @@
3
  # ============================================================================
4
  # Services:
5
  # api — FastAPI prediction server (port 8000)
6
- # streamlit — Drift monitoring dashboard (port 8501)
7
  # mlflow — MLflow tracking server (port 5000)
8
  # nginx — Reverse proxy (ports 80/443)
9
  #
@@ -39,31 +38,6 @@ services:
39
  networks:
40
  - kronector-net
41
 
42
- # --------------------------------------------------------------------------
43
- # Streamlit — Drift monitoring dashboard
44
- # --------------------------------------------------------------------------
45
- streamlit:
46
- build:
47
- context: .
48
- target: production
49
- container_name: kronector-dashboard
50
- command: >
51
- streamlit run ui/monitoring_dashboard.py
52
- --server.port=8501
53
- --server.address=0.0.0.0
54
- --server.headless=true
55
- --browser.gatherUsageStats=false
56
- ports:
57
- - "8501:8501"
58
- env_file:
59
- - .env
60
- volumes:
61
- - model_data:/app/mlruns
62
- - app_data:/app/data_output
63
- restart: unless-stopped
64
- networks:
65
- - kronector-net
66
-
67
  # --------------------------------------------------------------------------
68
  # MLflow — Experiment tracking & model registry
69
  # --------------------------------------------------------------------------
 
3
  # ============================================================================
4
  # Services:
5
  # api — FastAPI prediction server (port 8000)
 
6
  # mlflow — MLflow tracking server (port 5000)
7
  # nginx — Reverse proxy (ports 80/443)
8
  #
 
38
  networks:
39
  - kronector-net
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  # --------------------------------------------------------------------------
42
  # MLflow — Experiment tracking & model registry
43
  # --------------------------------------------------------------------------
frontend/.gitignore DELETED
@@ -1,41 +0,0 @@
1
- # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
-
3
- # dependencies
4
- /node_modules
5
- /.pnp
6
- .pnp.*
7
- .yarn/*
8
- !.yarn/patches
9
- !.yarn/plugins
10
- !.yarn/releases
11
- !.yarn/versions
12
-
13
- # testing
14
- /coverage
15
-
16
- # next.js
17
- /.next/
18
- /out/
19
-
20
- # production
21
- /build
22
-
23
- # misc
24
- .DS_Store
25
- *.pem
26
-
27
- # debug
28
- npm-debug.log*
29
- yarn-debug.log*
30
- yarn-error.log*
31
- .pnpm-debug.log*
32
-
33
- # env files (can opt-in for committing if needed)
34
- .env*
35
-
36
- # vercel
37
- .vercel
38
-
39
- # typescript
40
- *.tsbuildinfo
41
- next-env.d.ts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/AGENTS.md DELETED
@@ -1,5 +0,0 @@
1
- <!-- BEGIN:nextjs-agent-rules -->
2
- # This is NOT the Next.js you know
3
-
4
- This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
5
- <!-- END:nextjs-agent-rules -->
 
 
 
 
 
 
frontend/CLAUDE.md DELETED
@@ -1 +0,0 @@
1
- @AGENTS.md
 
 
frontend/README.md DELETED
@@ -1,36 +0,0 @@
1
- This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2
-
3
- ## Getting Started
4
-
5
- First, run the development server:
6
-
7
- ```bash
8
- npm run dev
9
- # or
10
- yarn dev
11
- # or
12
- pnpm dev
13
- # or
14
- bun dev
15
- ```
16
-
17
- Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18
-
19
- You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
20
-
21
- This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
22
-
23
- ## Learn More
24
-
25
- To learn more about Next.js, take a look at the following resources:
26
-
27
- - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28
- - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29
-
30
- You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
31
-
32
- ## Deploy on Vercel
33
-
34
- The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35
-
36
- Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app/favicon.ico DELETED
Binary file (25.9 kB)
 
frontend/app/globals.css DELETED
@@ -1,76 +0,0 @@
1
- @import "tailwindcss";
2
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap');
3
-
4
- @theme {
5
- --color-saas-bg: #000000;
6
- --color-saas-surface: #0A0A0A;
7
- --color-saas-card: #111111;
8
- --color-saas-border: #222222;
9
- --color-saas-border-hover: #333333;
10
- --color-saas-text-primary: #EDEDED;
11
- --color-saas-text-secondary: #888888;
12
- --color-saas-accent: #FFFFFF;
13
- --color-saas-blue: #0070F3;
14
- --color-saas-red: #E00000;
15
- --color-saas-green: #00D084;
16
-
17
- --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
18
- --font-mono: 'Geist Mono', Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
19
- }
20
-
21
- @layer base {
22
- :root {
23
- --background: 0 0% 0%;
24
- --foreground: 0 0% 93%;
25
- --card: 0 0% 7%;
26
- --card-foreground: 0 0% 93%;
27
- --popover: 0 0% 7%;
28
- --popover-foreground: 0 0% 93%;
29
- --primary: 0 0% 100%;
30
- --primary-foreground: 0 0% 0%;
31
- --secondary: 0 0% 13%;
32
- --secondary-foreground: 0 0% 93%;
33
- --muted: 0 0% 13%;
34
- --muted-foreground: 0 0% 53%;
35
- --accent: 0 0% 13%;
36
- --accent-foreground: 0 0% 93%;
37
- --destructive: 0 100% 44%;
38
- --destructive-foreground: 0 0% 100%;
39
- --border: 0 0% 13%;
40
- --input: 0 0% 13%;
41
- --ring: 0 0% 100%;
42
- --radius: 0.5rem;
43
- }
44
- }
45
-
46
- * {
47
- box-sizing: border-box;
48
- margin: 0;
49
- padding: 0;
50
- }
51
-
52
- body {
53
- background-color: var(--color-saas-bg);
54
- color: var(--color-saas-text-primary);
55
- font-family: var(--font-sans);
56
- min-height: 100vh;
57
- overflow-x: hidden;
58
- -webkit-font-smoothing: antialiased;
59
- -moz-osx-font-smoothing: grayscale;
60
- }
61
-
62
- /* Elegant minimalist scrollbar */
63
- ::-webkit-scrollbar {
64
- width: 8px;
65
- height: 8px;
66
- }
67
- ::-webkit-scrollbar-track {
68
- background: transparent;
69
- }
70
- ::-webkit-scrollbar-thumb {
71
- background: var(--color-saas-border);
72
- border-radius: 4px;
73
- }
74
- ::-webkit-scrollbar-thumb:hover {
75
- background: var(--color-saas-border-hover);
76
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app/layout.js DELETED
@@ -1,14 +0,0 @@
1
- import "./globals.css";
2
-
3
- export const metadata = {
4
- title: "KRONECTOR F1 Intelligence",
5
- description: "Predict F1 race outcomes using natural language queries powered by Hugging Face and MLflow.",
6
- };
7
-
8
- export default function RootLayout({ children }) {
9
- return (
10
- <html lang="en">
11
- <body>{children}</body>
12
- </html>
13
- );
14
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app/page.js DELETED
@@ -1,33 +0,0 @@
1
- import Navbar from "@/components/saas/Navbar";
2
- import Hero from "@/components/saas/Hero";
3
- import PrimaryActions from "@/components/saas/PrimaryActions";
4
- import UpcomingGP from "@/components/saas/UpcomingGP";
5
- import ShapExplanation from "@/components/saas/ShapExplanation";
6
- import DriverComparison from "@/components/saas/DriverComparison";
7
- import Telemetry from "@/components/saas/Telemetry";
8
- import Metrics from "@/components/saas/Metrics";
9
-
10
- export default function SaaSPage() {
11
- return (
12
- <div className="bg-[#000000] min-h-screen text-[#EDEDED] font-sans selection:bg-[#0070F3]/30">
13
- <Navbar />
14
-
15
- <main className="flex flex-col items-center w-full">
16
- <Hero />
17
- <PrimaryActions />
18
-
19
- {/* Everything below the fold */}
20
- <div className="w-full bg-[#0A0A0A] border-t border-[#222222]">
21
- <UpcomingGP />
22
- <ShapExplanation />
23
- </div>
24
-
25
- <div className="w-full">
26
- <DriverComparison />
27
- <Telemetry />
28
- <Metrics />
29
- </div>
30
- </main>
31
- </div>
32
- );
33
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/app/page.module.css DELETED
@@ -1,365 +0,0 @@
1
- .main {
2
- min-height: 100vh;
3
- padding: 4rem 2rem;
4
- display: flex;
5
- flex-direction: column;
6
- align-items: center;
7
- }
8
-
9
- .header {
10
- text-align: center;
11
- margin-bottom: 4rem;
12
- }
13
-
14
- .title {
15
- font-size: 4rem;
16
- margin-bottom: 1rem;
17
- line-height: 1.1;
18
- text-shadow: var(--shadow-glow);
19
- }
20
-
21
- .subtitle {
22
- font-size: 1.2rem;
23
- color: var(--text-secondary);
24
- max-width: 600px;
25
- margin: 0 auto;
26
- }
27
-
28
- /* Query Input Component */
29
- .queryContainer {
30
- width: 100%;
31
- max-width: 800px;
32
- position: relative;
33
- margin-bottom: 3rem;
34
- z-index: 10;
35
- }
36
-
37
- .inputWrapper {
38
- position: relative;
39
- width: 100%;
40
- border-radius: var(--border-radius-lg);
41
- background: var(--bg-glass);
42
- backdrop-filter: blur(20px);
43
- border: 1px solid rgba(0, 240, 255, 0.3);
44
- box-shadow: var(--shadow-glow);
45
- transition: var(--transition-smooth);
46
- }
47
-
48
- .inputWrapper:focus-within {
49
- box-shadow: 0 0 30px rgba(0, 240, 255, 0.5);
50
- border-color: var(--neon-cyan);
51
- }
52
-
53
- .input {
54
- width: 100%;
55
- background: transparent;
56
- border: none;
57
- padding: 1.5rem 4rem 1.5rem 2rem;
58
- color: var(--text-primary);
59
- font-size: 1.2rem;
60
- font-family: inherit;
61
- outline: none;
62
- }
63
-
64
- .input::placeholder {
65
- color: var(--text-muted);
66
- }
67
-
68
- .submitBtn {
69
- position: absolute;
70
- right: 1rem;
71
- top: 50%;
72
- transform: translateY(-50%);
73
- background: transparent;
74
- border: none;
75
- color: var(--neon-cyan);
76
- cursor: pointer;
77
- padding: 0.5rem;
78
- display: flex;
79
- align-items: center;
80
- justify-content: center;
81
- transition: var(--transition-fast);
82
- border-radius: 50%;
83
- }
84
-
85
- .submitBtn:hover {
86
- background: rgba(0, 240, 255, 0.1);
87
- box-shadow: var(--shadow-glow);
88
- }
89
-
90
- .submitBtn:disabled {
91
- color: var(--text-muted);
92
- cursor: not-allowed;
93
- background: transparent;
94
- box-shadow: none;
95
- }
96
-
97
- /* Loader */
98
- .loader {
99
- border: 3px solid rgba(0, 240, 255, 0.1);
100
- border-left-color: var(--neon-cyan);
101
- border-radius: 50%;
102
- width: 24px;
103
- height: 24px;
104
- animation: spin 1s linear infinite;
105
- }
106
-
107
- @keyframes spin {
108
- 0% { transform: rotate(0deg); }
109
- 100% { transform: rotate(360deg); }
110
- }
111
-
112
- /* Dashboard */
113
- .dashboardGrid {
114
- display: grid;
115
- grid-template-columns: 1fr 2fr;
116
- gap: 2rem;
117
- width: 100%;
118
- max-width: 1200px;
119
- }
120
-
121
- @media (max-width: 800px) {
122
- .dashboardGrid {
123
- grid-template-columns: 1fr;
124
- }
125
- }
126
-
127
- .panel {
128
- padding: 2rem;
129
- display: flex;
130
- flex-direction: column;
131
- }
132
-
133
- .panelTitle {
134
- font-size: 1.2rem;
135
- margin-bottom: 1.5rem;
136
- color: var(--neon-cyan);
137
- display: flex;
138
- align-items: center;
139
- gap: 0.5rem;
140
- text-transform: uppercase;
141
- letter-spacing: 2px;
142
- }
143
-
144
- /* Gauge */
145
- .gaugeContainer {
146
- display: flex;
147
- flex-direction: column;
148
- align-items: center;
149
- justify-content: center;
150
- padding: 2rem 0;
151
- }
152
-
153
- .gaugeCircle {
154
- position: relative;
155
- width: 200px;
156
- height: 200px;
157
- border-radius: 50%;
158
- background: conic-gradient(var(--neon-cyan) var(--prob), var(--bg-secondary) 0deg);
159
- display: flex;
160
- align-items: center;
161
- justify-content: center;
162
- box-shadow: var(--shadow-glow);
163
- margin-bottom: 1.5rem;
164
- }
165
-
166
- .gaugeCircle::before {
167
- content: "";
168
- position: absolute;
169
- inset: 15px;
170
- background: var(--bg-primary);
171
- border-radius: 50%;
172
- border: 1px solid rgba(0, 240, 255, 0.2);
173
- }
174
-
175
- .gaugeText {
176
- position: relative;
177
- font-size: 3.5rem;
178
- font-weight: 800;
179
- color: var(--text-primary);
180
- }
181
-
182
- .gaugeLabel {
183
- color: var(--text-secondary);
184
- font-size: 1.1rem;
185
- text-align: center;
186
- }
187
-
188
- .rating {
189
- margin-top: 1rem;
190
- padding: 0.25rem 1rem;
191
- border-radius: 20px;
192
- font-size: 0.9rem;
193
- font-weight: 600;
194
- text-transform: uppercase;
195
- letter-spacing: 1px;
196
- }
197
-
198
- .ratingGood {
199
- background: rgba(0, 255, 136, 0.1);
200
- color: var(--neon-green);
201
- border: 1px solid var(--neon-green);
202
- }
203
-
204
- .ratingBad {
205
- background: rgba(255, 42, 42, 0.1);
206
- color: var(--neon-red);
207
- border: 1px solid var(--neon-red);
208
- }
209
-
210
- /* Insight */
211
- .insightText {
212
- line-height: 1.8;
213
- font-size: 1.1rem;
214
- color: var(--text-primary);
215
- }
216
-
217
- .insightHighlight {
218
- color: var(--neon-cyan);
219
- font-weight: 600;
220
- }
221
-
222
- /* SHAP Bars */
223
- .shapContainer {
224
- display: flex;
225
- flex-direction: column;
226
- gap: 1rem;
227
- }
228
-
229
- .shapRow {
230
- display: grid;
231
- grid-template-columns: 150px 1fr 60px;
232
- align-items: center;
233
- gap: 1rem;
234
- }
235
-
236
- .shapLabel {
237
- font-size: 0.9rem;
238
- color: var(--text-secondary);
239
- text-align: right;
240
- white-space: nowrap;
241
- overflow: hidden;
242
- text-overflow: ellipsis;
243
- }
244
-
245
- .barTrack {
246
- height: 12px;
247
- background: rgba(255,255,255,0.05);
248
- border-radius: 6px;
249
- position: relative;
250
- overflow: hidden;
251
- }
252
-
253
- .barFill {
254
- height: 100%;
255
- position: absolute;
256
- top: 0;
257
- border-radius: 6px;
258
- transition: width 1s cubic-bezier(0.16, 1, 0.3, 1);
259
- }
260
-
261
- .barPositive {
262
- background: linear-gradient(90deg, transparent, var(--neon-cyan));
263
- left: 50%;
264
- box-shadow: var(--shadow-glow);
265
- }
266
-
267
- .barNegative {
268
- background: linear-gradient(270deg, transparent, var(--neon-red));
269
- right: 50%;
270
- box-shadow: var(--shadow-glow-red);
271
- }
272
-
273
- .shapValue {
274
- font-size: 0.85rem;
275
- font-family: var(--font-geist-mono), monospace;
276
- }
277
-
278
- /* Tabs */
279
- .tabsContainer {
280
- display: flex;
281
- gap: 1rem;
282
- margin-bottom: 2rem;
283
- z-index: 10;
284
- }
285
-
286
- .tabBtn {
287
- background: rgba(0, 0, 0, 0.3);
288
- border: 1px solid rgba(0, 240, 255, 0.3);
289
- color: var(--text-muted);
290
- padding: 0.75rem 2rem;
291
- border-radius: 20px;
292
- cursor: pointer;
293
- transition: var(--transition-fast);
294
- font-family: inherit;
295
- font-size: 1rem;
296
- text-transform: uppercase;
297
- letter-spacing: 1px;
298
- }
299
-
300
- .tabBtn:hover {
301
- background: rgba(0, 240, 255, 0.1);
302
- color: var(--neon-cyan);
303
- }
304
-
305
- .tabActive {
306
- background: rgba(0, 240, 255, 0.15);
307
- border-color: var(--neon-cyan);
308
- color: var(--neon-cyan);
309
- box-shadow: 0 0 15px rgba(0, 240, 255, 0.3);
310
- }
311
-
312
- /* Compare Form */
313
- .compareForm {
314
- display: flex;
315
- flex-direction: column;
316
- gap: 1.5rem;
317
- width: 100%;
318
- }
319
-
320
- .compareInputsRow {
321
- display: flex;
322
- align-items: center;
323
- justify-content: space-between;
324
- gap: 1rem;
325
- }
326
-
327
- .vsText {
328
- font-size: 1.5rem;
329
- font-weight: 800;
330
- color: var(--text-muted);
331
- font-style: italic;
332
- }
333
-
334
- .compareInputsRowSmall {
335
- display: flex;
336
- gap: 1rem;
337
- justify-content: center;
338
- }
339
-
340
- .compareSubmitBtn {
341
- background: linear-gradient(90deg, rgba(0, 240, 255, 0.2), rgba(0, 240, 255, 0.05));
342
- border: 1px solid var(--neon-cyan);
343
- color: var(--neon-cyan);
344
- padding: 1rem 2rem;
345
- border-radius: var(--border-radius-lg);
346
- cursor: pointer;
347
- font-family: inherit;
348
- font-size: 1.1rem;
349
- font-weight: 600;
350
- text-transform: uppercase;
351
- letter-spacing: 2px;
352
- transition: var(--transition-fast);
353
- }
354
-
355
- .compareSubmitBtn:hover:not(:disabled) {
356
- background: rgba(0, 240, 255, 0.2);
357
- box-shadow: var(--shadow-glow);
358
- }
359
-
360
- .compareSubmitBtn:disabled {
361
- border-color: rgba(0, 240, 255, 0.1);
362
- color: var(--text-muted);
363
- cursor: not-allowed;
364
- background: transparent;
365
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/DriverComparison.tsx DELETED
@@ -1,40 +0,0 @@
1
- "use client";
2
-
3
- import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from 'recharts';
4
-
5
- export default function DriverComparison() {
6
- const data = [
7
- { metric: 'Qualifying Pace', Norris: 98, Verstappen: 95 },
8
- { metric: 'Race Pace', Norris: 95, Verstappen: 97 },
9
- { metric: 'Win Rate', Norris: 45, Verstappen: 52 },
10
- { metric: 'Avg Finish', Norris: 2.1, Verstappen: 1.8 },
11
- ];
12
-
13
- return (
14
- <section id="driver-comparison" className="px-6 max-w-6xl mx-auto mb-24 pt-16">
15
- <div className="flex items-center gap-2 mb-8">
16
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-white"><path d="M16 3h5v5M4 20L21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg>
17
- <h2 className="text-2xl font-semibold text-white tracking-tight">Driver Comparison Results</h2>
18
- </div>
19
-
20
- <div className="bg-[#111111] border border-[#222222] rounded-xl p-8">
21
- <div className="h-[300px] w-full text-sm">
22
- <ResponsiveContainer width="100%" height="100%">
23
- <BarChart data={data} layout="vertical" margin={{ top: 0, right: 0, left: 20, bottom: 0 }}>
24
- <CartesianGrid strokeDasharray="3 3" stroke="#222222" horizontal={false} />
25
- <XAxis type="number" stroke="#888888" tick={{ fill: '#888888' }} />
26
- <YAxis dataKey="metric" type="category" stroke="#888888" tick={{ fill: '#888888' }} width={100} />
27
- <Tooltip
28
- cursor={{fill: '#222222', opacity: 0.4}}
29
- contentStyle={{ backgroundColor: '#000000', border: '1px solid #333333', color: '#fff', borderRadius: '8px' }}
30
- />
31
- <Legend wrapperStyle={{ paddingTop: '20px' }} />
32
- <Bar dataKey="Norris" fill="#FF8700" radius={[0, 4, 4, 0]} barSize={20} />
33
- <Bar dataKey="Verstappen" fill="#0070F3" radius={[0, 4, 4, 0]} barSize={20} />
34
- </BarChart>
35
- </ResponsiveContainer>
36
- </div>
37
- </div>
38
- </section>
39
- );
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/Hero.tsx DELETED
@@ -1,21 +0,0 @@
1
- "use client";
2
-
3
- export default function Hero() {
4
- return (
5
- <section className="pt-24 pb-12 px-6 max-w-5xl mx-auto text-center">
6
- <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#111111] border border-[#222222] text-xs font-medium text-[#888888] mb-8">
7
- <span className="w-2 h-2 rounded-full bg-[#0070F3]"></span>
8
- LightGBM Model v2.1 Deployed
9
- </div>
10
-
11
- <h1 className="text-5xl md:text-7xl font-bold tracking-tighter text-white mb-6">
12
- Formula 1 <br className="hidden md:block" />
13
- <span className="text-[#888888]">Intelligence Platform</span>
14
- </h1>
15
-
16
- <p className="text-lg md:text-xl text-[#888888] max-w-2xl mx-auto leading-relaxed">
17
- Predict race outcomes, compare drivers, analyze telemetry, and understand every prediction with mathematically-backed explainable AI.
18
- </p>
19
- </section>
20
- );
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/Metrics.tsx DELETED
@@ -1,30 +0,0 @@
1
- "use client";
2
-
3
- import { Database, ShieldCheck, Cpu } from "lucide-react";
4
-
5
- export default function Metrics() {
6
- const metrics = [
7
- { label: "Model Accuracy", value: "71.8%", icon: Cpu, sub: "Out of sample (2023-2025)" },
8
- { label: "Data Coverage", value: "8.2M", icon: Database, sub: "Rows of F1 Telemetry" },
9
- { label: "System Status", value: "Healthy", icon: ShieldCheck, sub: "No data drift detected" },
10
- ];
11
-
12
- return (
13
- <section className="px-6 max-w-6xl mx-auto pb-24 border-t border-[#222222] pt-16 mt-16">
14
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
15
- {metrics.map((m, i) => (
16
- <div key={i} className="flex items-start gap-4">
17
- <div className="p-3 bg-[#111111] border border-[#222222] rounded-lg">
18
- <m.icon className="w-5 h-5 text-white" />
19
- </div>
20
- <div>
21
- <div className="text-sm text-[#888888]">{m.label}</div>
22
- <div className="text-xl font-semibold text-white mt-1">{m.value}</div>
23
- <div className="text-xs text-[#666666] mt-1">{m.sub}</div>
24
- </div>
25
- </div>
26
- ))}
27
- </div>
28
- </section>
29
- );
30
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/Navbar.tsx DELETED
@@ -1,42 +0,0 @@
1
- "use client";
2
-
3
- import { Activity } from "lucide-react";
4
- import Link from "next/link";
5
-
6
- export default function Navbar() {
7
- return (
8
- <nav className="sticky top-0 z-50 w-full border-b border-[#222222] bg-[#000000]/80 backdrop-blur-md">
9
- <div className="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
10
- <div className="flex items-center gap-8">
11
- <Link href="/" className="flex items-center gap-2">
12
- <div className="w-6 h-6 bg-white text-black flex items-center justify-center rounded-sm font-bold text-sm">
13
- K
14
- </div>
15
- <span className="font-semibold tracking-tight text-white">Kronector</span>
16
- </Link>
17
-
18
- <div className="hidden md:flex items-center gap-6 text-sm font-medium text-[#888888]">
19
- <Link href="#dashboard" className="hover:text-white transition-colors">Dashboard</Link>
20
- <Link href="#predictions" className="text-white">Predictions</Link>
21
- <Link href="#compare" className="hover:text-white transition-colors">Compare</Link>
22
- <Link href="#telemetry" className="hover:text-white transition-colors">Telemetry</Link>
23
- <Link href="#metrics" className="hover:text-white transition-colors">Metrics</Link>
24
- </div>
25
- </div>
26
-
27
- <div className="flex items-center gap-6">
28
- <div className="hidden sm:flex items-center gap-2 text-xs font-mono text-[#888888] bg-[#111111] px-3 py-1.5 rounded-full border border-[#222222]">
29
- <span>Season 2026</span>
30
- </div>
31
- <div className="flex items-center gap-2 text-sm text-[#00D084] font-medium">
32
- <span className="relative flex h-2 w-2">
33
- <span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#00D084] opacity-75"></span>
34
- <span className="relative inline-flex rounded-full h-2 w-2 bg-[#00D084]"></span>
35
- </span>
36
- Live
37
- </div>
38
- </div>
39
- </div>
40
- </nav>
41
- );
42
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/PrimaryActions.tsx DELETED
@@ -1,93 +0,0 @@
1
- "use client";
2
-
3
- import { ArrowRight, Search, Zap } from "lucide-react";
4
- import { useState } from "react";
5
-
6
- export default function PrimaryActions() {
7
- const [query, setQuery] = useState("");
8
-
9
- const handlePredict = (e: React.FormEvent) => {
10
- e.preventDefault();
11
- if (query) {
12
- document.getElementById('upcoming-gp')?.scrollIntoView({ behavior: 'smooth' });
13
- }
14
- };
15
-
16
- const handleCompare = (e: React.FormEvent) => {
17
- e.preventDefault();
18
- document.getElementById('driver-comparison')?.scrollIntoView({ behavior: 'smooth' });
19
- };
20
-
21
- return (
22
- <section className="px-6 max-w-6xl mx-auto mb-24">
23
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
24
-
25
- {/* Predict Card */}
26
- <div className="bg-[#111111] border border-[#222222] hover:border-[#333333] transition-colors rounded-xl p-8 flex flex-col justify-between h-[320px]">
27
- <div>
28
- <div className="flex items-center gap-2 text-white font-medium mb-2">
29
- <Zap className="w-5 h-5 text-[#0070F3]" />
30
- Predict a Race Outcome
31
- </div>
32
- <p className="text-[#888888] text-sm mb-6">Ask any natural language question about an upcoming race.</p>
33
- </div>
34
-
35
- <form onSubmit={handlePredict} className="mt-auto">
36
- <div className="relative mb-4">
37
- <Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-[#888888]" />
38
- <input
39
- type="text"
40
- value={query}
41
- onChange={(e) => setQuery(e.target.value)}
42
- placeholder="Who will win the Canadian GP?"
43
- className="w-full bg-[#000000] border border-[#222222] rounded-lg py-4 pl-12 pr-4 text-white placeholder:text-[#444444] focus:outline-none focus:border-[#0070F3] focus:ring-1 focus:ring-[#0070F3] transition-all text-lg"
44
- />
45
- </div>
46
- <button type="submit" className="w-full bg-white text-black hover:bg-gray-200 font-medium py-3 rounded-lg flex items-center justify-center gap-2 transition-colors">
47
- Predict Outcome <ArrowRight className="w-4 h-4" />
48
- </button>
49
- <div className="flex gap-3 mt-4 text-xs text-[#666666]">
50
- <span className="hover:text-[#888888] cursor-pointer">Who wins Monaco?</span>
51
- <span className="hover:text-[#888888] cursor-pointer">Predict Silverstone podium</span>
52
- </div>
53
- </form>
54
- </div>
55
-
56
- {/* Compare Card */}
57
- <div className="bg-[#111111] border border-[#222222] hover:border-[#333333] transition-colors rounded-xl p-8 flex flex-col justify-between h-[320px]">
58
- <div>
59
- <div className="flex items-center gap-2 text-white font-medium mb-2">
60
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-[#00D084]"><path d="M16 3h5v5M4 20L21 3M21 16v5h-5M15 15l6 6M4 4l5 5"/></svg>
61
- Compare Drivers
62
- </div>
63
- <p className="text-[#888888] text-sm mb-6">Analyze head-to-head performance across metrics.</p>
64
- </div>
65
-
66
- <form onSubmit={handleCompare} className="mt-auto">
67
- <div className="flex items-center gap-4 mb-6">
68
- <div className="flex-1">
69
- <select className="w-full bg-[#000000] border border-[#222222] rounded-lg p-4 text-white focus:outline-none focus:border-[#00D084] appearance-none cursor-pointer">
70
- <option>Norris</option>
71
- <option>Verstappen</option>
72
- <option>Leclerc</option>
73
- </select>
74
- </div>
75
- <div className="text-[#888888] font-mono text-sm">VS</div>
76
- <div className="flex-1">
77
- <select className="w-full bg-[#000000] border border-[#222222] rounded-lg p-4 text-white focus:outline-none focus:border-[#00D084] appearance-none cursor-pointer">
78
- <option>Verstappen</option>
79
- <option>Norris</option>
80
- <option>Leclerc</option>
81
- </select>
82
- </div>
83
- </div>
84
- <button type="submit" className="w-full bg-[#222222] text-white hover:bg-[#333333] border border-[#333333] font-medium py-3 rounded-lg flex items-center justify-center gap-2 transition-colors">
85
- Compare Stats
86
- </button>
87
- </form>
88
- </div>
89
-
90
- </div>
91
- </section>
92
- );
93
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/ShapExplanation.tsx DELETED
@@ -1,52 +0,0 @@
1
- "use client";
2
-
3
- import { Brain } from "lucide-react";
4
-
5
- export default function ShapExplanation() {
6
- const factors = [
7
- { name: "Grid Position", value: "+3.18", type: "positive" },
8
- { name: "Driver Form (Last 3)", value: "+1.39", type: "positive" },
9
- { name: "Team Perf Index", value: "+1.01", type: "positive" },
10
- { name: "Wet Weather Risk", value: "-0.85", type: "negative" },
11
- { name: "Reliability Concerns", value: "-1.22", type: "negative" },
12
- ];
13
-
14
- return (
15
- <section className="px-6 max-w-6xl mx-auto mb-24">
16
- <div className="flex items-center gap-2 mb-8">
17
- <Brain className="w-5 h-5 text-white" />
18
- <h2 className="text-2xl font-semibold text-white tracking-tight">Why The Model Thinks This</h2>
19
- </div>
20
-
21
- <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
22
- <div className="bg-[#111111] border border-[#222222] rounded-xl p-8 flex flex-col justify-center items-center text-center">
23
- <div className="text-sm text-[#888888] mb-2 uppercase tracking-wider">Prediction Confidence</div>
24
- <div className="text-5xl font-mono font-medium text-white">92%</div>
25
- <div className="text-xs text-[#00D084] mt-2 bg-[#00D084]/10 px-2 py-1 rounded">MATHEMATICALLY SOUND</div>
26
- </div>
27
-
28
- <div className="md:col-span-2 bg-[#111111] border border-[#222222] rounded-xl p-8">
29
- <div className="text-sm text-[#888888] mb-6 uppercase tracking-wider">Feature Contributions (SHAP)</div>
30
-
31
- <div className="space-y-4">
32
- {factors.map((f, i) => (
33
- <div key={i} className="flex justify-between items-center text-sm">
34
- <span className="text-[#EDEDED]">{f.name}</span>
35
- <div className="flex items-center gap-3">
36
- <span className={`font-mono ${f.type === 'positive' ? 'text-[#00D084]' : 'text-[#E00000]'}`}>{f.value}</span>
37
- <div className="w-32 h-1.5 bg-[#222222] rounded-full overflow-hidden flex justify-end">
38
- {f.type === 'positive' ? (
39
- <div className="h-full bg-[#00D084] w-full" />
40
- ) : (
41
- <div className="h-full bg-[#E00000] w-[40%]" />
42
- )}
43
- </div>
44
- </div>
45
- </div>
46
- ))}
47
- </div>
48
- </div>
49
- </div>
50
- </section>
51
- );
52
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/Telemetry.tsx DELETED
@@ -1,46 +0,0 @@
1
- "use client";
2
-
3
- import { Activity } from "lucide-react";
4
- import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
5
-
6
- export default function Telemetry() {
7
- const data = Array.from({ length: 50 }).map((_, i) => ({
8
- lap: i + 1,
9
- ver: 80 + Math.random() * 2 + (i * 0.05), // Verstappen lap times
10
- nor: 80.5 + Math.random() * 1.5 + (i * 0.04), // Norris lap times
11
- }));
12
-
13
- return (
14
- <section className="px-6 max-w-6xl mx-auto mb-24">
15
- <div className="flex items-center gap-2 mb-8">
16
- <Activity className="w-5 h-5 text-white" />
17
- <h2 className="text-2xl font-semibold text-white tracking-tight">Telemetry Analysis</h2>
18
- </div>
19
-
20
- <div className="bg-[#111111] border border-[#222222] rounded-xl p-8">
21
- <div className="flex items-center justify-between mb-8">
22
- <div className="text-sm text-[#888888]">Simulated Race Pace & Tire Degradation</div>
23
- <div className="flex gap-4 text-sm font-medium">
24
- <div className="flex items-center gap-2"><div className="w-3 h-3 rounded-full bg-[#0070F3]" /> Verstappen</div>
25
- <div className="flex items-center gap-2"><div className="w-3 h-3 rounded-full bg-[#FF8700]" /> Norris</div>
26
- </div>
27
- </div>
28
-
29
- <div className="h-[300px] w-full text-sm">
30
- <ResponsiveContainer width="100%" height="100%">
31
- <LineChart data={data} margin={{ top: 5, right: 0, left: -20, bottom: 0 }}>
32
- <CartesianGrid strokeDasharray="3 3" stroke="#222222" vertical={false} />
33
- <XAxis dataKey="lap" stroke="#888888" tick={{ fill: '#888888' }} />
34
- <YAxis domain={['auto', 'auto']} stroke="#888888" tick={{ fill: '#888888' }} />
35
- <Tooltip
36
- contentStyle={{ backgroundColor: '#000000', border: '1px solid #333333', color: '#fff', borderRadius: '8px' }}
37
- />
38
- <Line type="monotone" dataKey="ver" name="Verstappen (s)" stroke="#0070F3" strokeWidth={2} dot={false} />
39
- <Line type="monotone" dataKey="nor" name="Norris (s)" stroke="#FF8700" strokeWidth={2} dot={false} />
40
- </LineChart>
41
- </ResponsiveContainer>
42
- </div>
43
- </div>
44
- </section>
45
- );
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/saas/UpcomingGP.tsx DELETED
@@ -1,40 +0,0 @@
1
- "use client";
2
-
3
- import { CloudRain, MapPin, Trophy } from "lucide-react";
4
-
5
- export default function UpcomingGP() {
6
- const predictions = [
7
- { name: "Norris", prob: 42, color: "bg-[#FF8700]" },
8
- { name: "Verstappen", prob: 31, color: "bg-[#0070F3]" },
9
- { name: "Leclerc", prob: 17, color: "bg-[#E00000]" },
10
- { name: "Piastri", prob: 10, color: "bg-[#FF8700]" },
11
- ];
12
-
13
- return (
14
- <section id="upcoming-gp" className="px-6 max-w-6xl mx-auto mb-16 pt-16">
15
- <div className="flex items-center gap-2 mb-8">
16
- <Trophy className="w-5 h-5 text-white" />
17
- <h2 className="text-2xl font-semibold text-white tracking-tight">Predicted Podium</h2>
18
- </div>
19
-
20
- <div className="bg-[#111111] border border-[#222222] rounded-xl p-8">
21
- <div className="flex items-center gap-6 mb-8 text-sm text-[#888888] border-b border-[#222222] pb-6">
22
- <span className="flex items-center gap-2"><MapPin className="w-4 h-4" /> Montreal, Canada</span>
23
- <span className="flex items-center gap-2"><CloudRain className="w-4 h-4" /> 40% Rain Probability</span>
24
- </div>
25
-
26
- <div className="space-y-6">
27
- {predictions.map((p, i) => (
28
- <div key={i} className="flex items-center gap-6">
29
- <div className="w-24 text-sm font-medium text-white">{p.name}</div>
30
- <div className="flex-1 h-3 bg-[#222222] rounded-full overflow-hidden">
31
- <div className={`h-full ${p.color} transition-all duration-1000 ease-out`} style={{ width: `${p.prob}%` }} />
32
- </div>
33
- <div className="w-12 text-right text-sm font-mono text-[#888888]">{p.prob}%</div>
34
- </div>
35
- ))}
36
- </div>
37
- </div>
38
- </section>
39
- );
40
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/ui/button.tsx DELETED
@@ -1,52 +0,0 @@
1
- import { Slot } from "@radix-ui/react-slot";
2
- import { cva, type VariantProps } from "class-variance-authority";
3
- import * as React from "react";
4
-
5
- import { cn } from "@/lib/utils";
6
-
7
- const buttonVariants = cva(
8
- "inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-medium transition-colors outline-offset-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-ring/70 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
9
- {
10
- variants: {
11
- variant: {
12
- default: "bg-primary text-primary-foreground shadow-sm shadow-black/5 hover:bg-primary/90",
13
- destructive:
14
- "bg-destructive text-destructive-foreground shadow-sm shadow-black/5 hover:bg-destructive/90",
15
- outline:
16
- "border border-input bg-background shadow-sm shadow-black/5 hover:bg-accent hover:text-accent-foreground",
17
- secondary:
18
- "bg-secondary text-secondary-foreground shadow-sm shadow-black/5 hover:bg-secondary/80",
19
- ghost: "hover:bg-accent hover:text-accent-foreground",
20
- link: "text-primary underline-offset-4 hover:underline",
21
- },
22
- size: {
23
- default: "h-9 px-4 py-2",
24
- sm: "h-8 rounded-lg px-3 text-xs",
25
- lg: "h-10 rounded-lg px-8",
26
- icon: "h-9 w-9",
27
- },
28
- },
29
- defaultVariants: {
30
- variant: "default",
31
- size: "default",
32
- },
33
- },
34
- );
35
-
36
- export interface ButtonProps
37
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
- VariantProps<typeof buttonVariants> {
39
- asChild?: boolean;
40
- }
41
-
42
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
- ({ className, variant, size, asChild = false, ...props }, ref) => {
44
- const Comp = asChild ? Slot : "button";
45
- return (
46
- <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
47
- );
48
- },
49
- );
50
- Button.displayName = "Button";
51
-
52
- export { Button, buttonVariants };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/ui/popover.tsx DELETED
@@ -1,40 +0,0 @@
1
- "use client";
2
-
3
- import * as PopoverPrimitive from "@radix-ui/react-popover";
4
- import * as React from "react";
5
-
6
- import { cn } from "@/lib/utils";
7
-
8
- const Popover = PopoverPrimitive.Root;
9
-
10
- const PopoverTrigger = PopoverPrimitive.Trigger;
11
-
12
- const PopoverAnchor = PopoverPrimitive.Anchor;
13
-
14
- const PopoverContent = React.forwardRef<
15
- React.ElementRef<typeof PopoverPrimitive.Content>,
16
- React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & {
17
- showArrow?: boolean;
18
- }
19
- >(({ className, align = "center", sideOffset = 4, showArrow = false, ...props }, ref) => (
20
- <PopoverPrimitive.Portal>
21
- <PopoverPrimitive.Content
22
- ref={ref}
23
- align={align}
24
- sideOffset={sideOffset}
25
- className={cn(
26
- "z-50 max-h-[var(--radix-popover-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-popover p-4 text-popover-foreground shadow-lg shadow-black/5 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
27
- className,
28
- )}
29
- {...props}
30
- >
31
- {props.children}
32
- {showArrow && (
33
- <PopoverPrimitive.Arrow className="-my-px fill-popover drop-shadow-[0_1px_0_hsl(var(--border))]" />
34
- )}
35
- </PopoverPrimitive.Content>
36
- </PopoverPrimitive.Portal>
37
- ));
38
- PopoverContent.displayName = PopoverPrimitive.Content.displayName;
39
-
40
- export { Popover, PopoverAnchor, PopoverContent, PopoverTrigger };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/ui/ruixen-query-box.tsx DELETED
@@ -1,80 +0,0 @@
1
- "use client";
2
-
3
- import { SendHorizonal } from "lucide-react";
4
- import { useState } from "react";
5
- import { cn } from "@/lib/utils";
6
- import { Textarea } from "@/components/ui/textarea";
7
- import { useAutoResizeTextarea } from "@/hooks/use-auto-resize-textarea";
8
-
9
- export default function RuixenQueryBox({ onSubmit }: { onSubmit: (query: string) => void }) {
10
- const { textareaRef, adjustHeight } = useAutoResizeTextarea({
11
- minHeight: 56,
12
- maxHeight: 220,
13
- });
14
-
15
- const [inputValue, setInputValue] = useState("");
16
-
17
- const handleSend = () => {
18
- if (!inputValue.trim()) return;
19
- // Call the parent's submit handler instead of just console.log
20
- onSubmit(inputValue);
21
- setInputValue("");
22
- adjustHeight(true);
23
- };
24
-
25
- return (
26
- <div className="w-full py-6">
27
- <div
28
- className="relative w-full mx-auto bg-white rounded-2xl border shadow-sm overflow-hidden"
29
- style={{
30
- backgroundImage:
31
- "url('https://pub-940ccf6255b54fa799a9b01050e6c227.r2.dev/ruixen_chat_gradient.png')",
32
- backgroundSize: "cover",
33
- backgroundPosition: "center",
34
- borderColor: "var(--border-glass)"
35
- }}
36
- >
37
- <Textarea
38
- id="ai-textarea"
39
- ref={textareaRef}
40
- placeholder="Ask KRONECTOR about F1 stats, predictions, or telemetry..."
41
- className={cn(
42
- "w-full resize-none border-none bg-transparent",
43
- "text-base text-white placeholder:text-gray-400",
44
- "px-5 py-4 pr-14 rounded-2xl leading-[1.4]",
45
- "transition-all focus-visible:ring-0 focus-visible:ring-offset-0",
46
- "min-h-[56px]"
47
- )}
48
- value={inputValue}
49
- onChange={(e) => {
50
- setInputValue(e.target.value);
51
- adjustHeight();
52
- }}
53
- onKeyDown={(e) => {
54
- if (e.key === "Enter" && !e.shiftKey) {
55
- e.preventDefault();
56
- handleSend();
57
- }
58
- }}
59
- />
60
-
61
- {/* Icon Buttons */}
62
- <div className="absolute bottom-3 right-3 flex items-center gap-2">
63
- <button
64
- type="button"
65
- onClick={handleSend}
66
- disabled={!inputValue.trim()}
67
- className={cn(
68
- "p-2 rounded-full transition-all duration-200 border",
69
- inputValue.trim()
70
- ? "bg-cyan-500 hover:bg-cyan-400 text-black border-transparent shadow-[0_0_15px_rgba(0,240,255,0.4)]"
71
- : "bg-black/20 text-gray-500 border-white/5 cursor-not-allowed"
72
- )}
73
- >
74
- <SendHorizonal className="w-4 h-4" />
75
- </button>
76
- </div>
77
- </div>
78
- </div>
79
- );
80
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/components/ui/textarea.tsx DELETED
@@ -1,21 +0,0 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "@/lib/utils";
4
-
5
- const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
6
- ({ className, ...props }, ref) => {
7
- return (
8
- <textarea
9
- className={cn(
10
- "flex w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground shadow-sm shadow-black/5 transition-shadow placeholder:text-muted-foreground/70 focus-visible:border-ring focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/20 disabled:cursor-not-allowed disabled:opacity-50",
11
- className,
12
- )}
13
- ref={ref}
14
- {...props}
15
- />
16
- );
17
- },
18
- );
19
- Textarea.displayName = "Textarea";
20
-
21
- export { Textarea };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/desktop.html ADDED
@@ -0,0 +1,550 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html class="dark" lang="en"><head>
4
+ <meta charset="utf-8"/>
5
+ <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
6
+ <title>Kronector | Formula 1 Intelligence</title>
7
+ <!-- Fonts -->
8
+ <link href="https://fonts.googleapis.com" rel="preconnect"/>
9
+ <link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800&amp;family=JetBrains+Mono:wght@400;700&amp;display=swap" rel="stylesheet"/>
11
+ <!-- Material Symbols -->
12
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
13
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
14
+ <!-- Tailwind Config -->
15
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
16
+ <script id="tailwind-config">
17
+ tailwind.config = {
18
+ darkMode: "class",
19
+ theme: {
20
+ extend: {
21
+ "colors": {
22
+ "surface-dim": "#131313",
23
+ "on-primary-fixed": "#400200",
24
+ "secondary": "#ddfcff",
25
+ "primary": "#ffb4a7",
26
+ "on-error-container": "#ffdad6",
27
+ "surface-container": "#201f1f",
28
+ "on-secondary-fixed": "#002022",
29
+ "on-secondary-fixed-variant": "#004f54",
30
+ "tertiary-container": "#909191",
31
+ "inverse-on-surface": "#313030",
32
+ "primary-fixed-dim": "#ffb4a7",
33
+ "inverse-primary": "#be0e00",
34
+ "on-tertiary-container": "#282a2a",
35
+ "on-secondary-container": "#006a70",
36
+ "tertiary-fixed-dim": "#c6c6c7",
37
+ "outline": "#b08780",
38
+ "outline-variant": "#5f3e39",
39
+ "primary-container": "#ff553d",
40
+ "on-primary-fixed-variant": "#910900",
41
+ "secondary-fixed": "#74f5ff",
42
+ "surface-container-lowest": "#0e0e0e",
43
+ "on-surface-variant": "#eabcb4",
44
+ "tertiary-fixed": "#e2e2e2",
45
+ "surface-container-high": "#2a2a2a",
46
+ "on-tertiary-fixed-variant": "#454747",
47
+ "on-tertiary": "#2f3131",
48
+ "on-tertiary-fixed": "#1a1c1c",
49
+ "background": "#131313",
50
+ "on-error": "#690005",
51
+ "surface-variant": "#353534",
52
+ "surface-bright": "#3a3939",
53
+ "on-surface": "#e5e2e1",
54
+ "surface-container-low": "#1c1b1b",
55
+ "on-secondary": "#00363a",
56
+ "primary-fixed": "#ffdad4",
57
+ "on-primary-container": "#5b0300",
58
+ "on-primary": "#670400",
59
+ "surface": "#131313",
60
+ "secondary-container": "#00f1fe",
61
+ "on-background": "#e5e2e1",
62
+ "inverse-surface": "#e5e2e1",
63
+ "secondary-fixed-dim": "#00dbe7",
64
+ "error-container": "#93000a",
65
+ "surface-tint": "#ffb4a7",
66
+ "tertiary": "#c6c6c7",
67
+ "surface-container-highest": "#353534",
68
+ "error": "#ffb4ab"
69
+ },
70
+ "borderRadius": {
71
+ "DEFAULT": "0.25rem",
72
+ "lg": "0.5rem",
73
+ "xl": "0.75rem",
74
+ "full": "9999px"
75
+ },
76
+ "spacing": {
77
+ "margin-mobile": "16px",
78
+ "container-max": "1440px",
79
+ "margin-desktop": "32px",
80
+ "gutter": "16px",
81
+ "unit": "4px"
82
+ },
83
+ "fontFamily": {
84
+ "headline-md": ["Inter"],
85
+ "body-main": ["Inter"],
86
+ "label-caps": ["JetBrains Mono"],
87
+ "display-lg-mobile": ["Inter"],
88
+ "body-fixed": ["JetBrains Mono"],
89
+ "display-lg": ["Inter"]
90
+ },
91
+ "fontSize": {
92
+ "headline-md": ["24px", { "lineHeight": "1.3", "letterSpacing": "-0.01em", "fontWeight": "700" }],
93
+ "body-main": ["16px", { "lineHeight": "1.6", "letterSpacing": "0em", "fontWeight": "400" }],
94
+ "label-caps": ["11px", { "lineHeight": "1", "letterSpacing": "0.1em", "fontWeight": "700" }],
95
+ "display-lg-mobile": ["32px", { "lineHeight": "1.2", "letterSpacing": "-0.02em", "fontWeight": "800" }],
96
+ "body-fixed": ["14px", { "lineHeight": "1.5", "letterSpacing": "0em", "fontWeight": "400" }],
97
+ "display-lg": ["48px", { "lineHeight": "1.1", "letterSpacing": "-0.04em", "fontWeight": "800" }]
98
+ }
99
+ }
100
+ }
101
+ }
102
+ </script>
103
+ <style>
104
+ /* Custom Carbon UI Grid Pattern */
105
+ .bg-grid-pattern {
106
+ background-image:
107
+ linear-gradient(to right, rgba(95, 62, 57, 0.05) 1px, transparent 1px),
108
+ linear-gradient(to bottom, rgba(95, 62, 57, 0.05) 1px, transparent 1px);
109
+ background-size: 32px 32px;
110
+ }
111
+
112
+ /* Terminal Glow Effects */
113
+ .terminal-glow {
114
+ box-shadow: 0 0 15px rgba(0, 241, 254, 0.1);
115
+ }
116
+
117
+ /* SHAP Bar Animation (Simulated) */
118
+ @keyframes shapExpand {
119
+ from { width: 0; }
120
+ to { width: var(--target-width); }
121
+ }
122
+ .shap-bar {
123
+ animation: shapExpand 1.5s ease-out forwards;
124
+ }
125
+
126
+ /* Flow Diagram Pulse */
127
+ @keyframes linePulse {
128
+ 0% { opacity: 0.3; }
129
+ 50% { opacity: 1; }
130
+ 100% { opacity: 0.3; }
131
+ }
132
+ .flow-line {
133
+ animation: linePulse 2s infinite;
134
+ }
135
+ </style>
136
+ </head>
137
+ <body class="bg-surface-dim text-on-surface font-body-main antialiased min-h-screen flex flex-col selection:bg-secondary-container selection:text-on-secondary-container">
138
+ <!-- 1. TopNavBar -->
139
+ <header class="bg-surface-dim/80 backdrop-blur-xl w-full flex justify-between items-center px-margin-desktop h-16 z-50 border-b border-outline-variant/30 sticky top-0">
140
+ <div class="flex items-center gap-gutter">
141
+ <span class="font-display-lg text-display-lg-mobile md:text-[24px] font-extrabold uppercase tracking-tighter text-on-surface">KRONECTOR</span>
142
+ <div id="health-indicator" class="hidden md:flex items-center gap-2 px-3 py-1 bg-surface-container rounded-full border border-surface-variant cursor-pointer" title="Click to check API health">
143
+ <span id="health-dot" class="w-2 h-2 rounded-full bg-secondary-fixed-dim animate-pulse"></span>
144
+ <span id="health-label" class="font-label-caps text-label-caps text-secondary-fixed-dim uppercase">Checking...</span>
145
+ </div>
146
+ </div>
147
+ <nav class="hidden md:flex gap-8">
148
+ <a class="text-primary font-bold border-b-2 border-primary pb-1 font-label-caps text-label-caps hover:text-secondary-fixed transition-colors duration-200" href="#section-predictions">Predictions</a>
149
+ <a class="text-on-surface-variant font-medium font-label-caps text-label-caps hover:text-secondary-fixed transition-colors duration-200" href="#section-features">Model Proof</a>
150
+ <a class="text-on-surface-variant font-medium font-label-caps text-label-caps hover:text-secondary-fixed transition-colors duration-200" href="#section-architecture">Architecture</a>
151
+ <a class="text-on-surface-variant font-medium font-label-caps text-label-caps hover:text-secondary-fixed transition-colors duration-200" href="#section-cta">API</a>
152
+ </nav>
153
+ <button id="nav-run-btn" class="bg-primary-container text-on-primary-container hover:bg-primary-container/80 transition-colors px-4 py-2 rounded font-label-caps text-label-caps hidden md:block active:scale-95">Run Prediction</button>
154
+ <!-- Mobile Menu Icon (Placeholder) -->
155
+ <button class="md:hidden text-on-surface">
156
+ <span class="material-symbols-outlined">menu</span>
157
+ </button>
158
+ </header>
159
+ <main class="flex-grow">
160
+ <!-- 2. Hero Section -->
161
+ <section id="section-predictions" class="relative pt-20 pb-16 px-margin-mobile md:px-margin-desktop max-w-container-max mx-auto overflow-hidden scroll-mt-20">
162
+ <div class="absolute inset-0 bg-grid-pattern pointer-events-none"></div>
163
+ <div class="grid grid-cols-1 lg:grid-cols-12 gap-12 items-center relative z-10">
164
+ <!-- Text Content -->
165
+ <div class="lg:col-span-5 flex flex-col gap-6">
166
+ <div class="inline-flex items-center gap-2 px-3 py-1 bg-surface-container border border-surface-variant rounded-full w-max">
167
+ <span class="material-symbols-outlined text-secondary-fixed-dim text-sm" style="font-variation-settings: 'FILL' 1;">speed</span>
168
+ <span class="font-label-caps text-label-caps text-on-surface-variant">Telemetry Ingestion Active</span>
169
+ </div>
170
+ <h1 class="font-display-lg text-display-lg-mobile md:text-display-lg text-on-surface leading-tight">Predict the race before lights out</h1>
171
+ <p class="font-body-main text-body-main text-on-surface-variant">Kronector turns Formula 1 telemetry, qualifying pace, driver form, and model explainability into race-winning predictions before the grid forms.</p>
172
+ <div class="flex flex-wrap gap-4 pt-4">
173
+ <button id="hero-run-btn" class="bg-primary-container text-on-primary-container px-6 py-3 rounded hover:bg-primary/90 transition-colors font-headline-md text-[16px]">Run prediction</button>
174
+ <button id="hero-proof-btn" class="border border-secondary-container text-secondary-fixed-dim px-6 py-3 rounded hover:bg-secondary-container/10 transition-colors font-body-fixed text-body-fixed flex items-center gap-2">
175
+ View model proof
176
+ <span class="material-symbols-outlined text-sm">arrow_forward</span>
177
+ </button>
178
+ </div>
179
+ </div>
180
+ <!-- Product Mock (Terminal) -->
181
+ <div id="terminal" class="lg:col-span-7 bg-[#000000] border border-secondary-container rounded-lg terminal-glow flex flex-col overflow-hidden shadow-2xl">
182
+ <!-- Terminal Header -->
183
+ <div class="h-8 bg-surface-container flex items-center px-4 border-b border-surface-variant gap-2">
184
+ <div class="w-3 h-3 rounded-full bg-surface-variant"></div>
185
+ <div class="w-3 h-3 rounded-full bg-surface-variant"></div>
186
+ <div class="w-3 h-3 rounded-full bg-surface-variant"></div>
187
+ <span class="ml-4 font-body-fixed text-[10px] text-on-surface-variant">kronector-inference-node-01 — zsh</span>
188
+ <span id="terminal-confidence" class="mr-4 font-label-caps text-[10px] text-secondary-fixed-dim">Confidence Interval: —</span><span id="terminal-gp-label" class="ml-auto font-label-caps text-[10px] text-primary-container">Ready for Query</span>
189
+ </div>
190
+ <div class="p-4 grid grid-cols-1 md:grid-cols-2 gap-px bg-surface-variant">
191
+ <!-- Win Probability Pane -->
192
+ <div id="pane-prediction" class="bg-[#000000] p-4 col-span-1 md:col-span-2 flex flex-col gap-2 overflow-x-auto custom-scrollbar">
193
+ <h3 class="font-label-caps text-label-caps text-secondary-fixed-dim mb-2 uppercase border-b border-surface-variant pb-1">Win Probability Model</h3>
194
+ <div id="prediction-content">
195
+ <p class="font-body-fixed text-[12px] text-on-surface-variant/60 italic">Enter a query below to get a live prediction...</p>
196
+ </div>
197
+ </div>
198
+ <!-- SHAP Pane -->
199
+ <div id="pane-shap" class="bg-[#000000] p-4 flex flex-col gap-2 border-t border-surface-variant md:border-t-0 md:border-l">
200
+ <h3 class="font-label-caps text-label-caps text-secondary-fixed-dim mb-2 uppercase border-b border-surface-variant pb-1">SHAP Features</h3>
201
+ <div id="shap-content" class="flex flex-col gap-2 font-body-fixed text-[11px]">
202
+ <p class="text-on-surface-variant/60 italic text-[11px]">Awaiting prediction...</p>
203
+ </div>
204
+ </div>
205
+ <!-- Race Radio Pane -->
206
+ <div id="pane-radio" class="bg-[#000000] p-4 col-span-1 md:col-span-2 border-t border-surface-variant">
207
+ <h3 class="font-label-caps text-label-caps text-secondary-fixed-dim mb-2 uppercase">Agentic Race-Radio</h3>
208
+ <div id="radio-content" class="font-body-fixed text-[12px] text-tertiary-fixed-dim flex flex-col gap-1 opacity-80">
209
+ <div><span class="text-on-surface-variant/50">—</span> Waiting for agentic synthesis...</div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+ <!-- Prompt Input -->
214
+ <div class="bg-surface-container border-t border-surface-variant p-3 flex items-center gap-3">
215
+ <span class="font-body-fixed text-secondary-fixed-dim">❯</span>
216
+ <input id="terminal-input" class="bg-transparent border-none text-on-surface font-body-fixed text-[13px] flex-grow focus:ring-0 p-0" type="text" placeholder="Who wins the 2026 Canadian GP?" autocomplete="off"/>
217
+ <button id="terminal-send" class="text-on-surface-variant hover:text-secondary-fixed-dim transition-colors">
218
+ <span class="material-symbols-outlined text-sm">send</span>
219
+ </button>
220
+ </div>
221
+ </div>
222
+ </div>
223
+ </section>
224
+ <!-- 3. Metrics Strip -->
225
+ <section id="section-metrics" class="border-y border-outline-variant/30 bg-surface-dim/50 py-8 px-margin-mobile md:px-margin-desktop">
226
+ <div class="max-w-container-max mx-auto grid grid-cols-2 md:grid-cols-4 gap-8 divide-x divide-outline-variant/30">
227
+ <div class="pl-4 border-l-2 border-secondary-fixed-dim flex flex-col gap-1">
228
+ <span class="font-headline-md text-headline-md text-on-surface">71%</span>
229
+ <span class="font-label-caps text-label-caps text-on-surface-variant uppercase">Winner Accuracy</span>
230
+ </div>
231
+ <div class="pl-4 md:pl-8 border-l-2 border-primary-container flex flex-col gap-1">
232
+ <span class="font-headline-md text-headline-md text-on-surface">0.94</span>
233
+ <span class="font-label-caps text-label-caps text-on-surface-variant uppercase">ROC AUC</span>
234
+ </div>
235
+ <div class="pl-4 md:pl-8 border-l-2 border-secondary-fixed-dim flex flex-col gap-1">
236
+ <span class="font-headline-md text-headline-md text-on-surface">4-Agent</span>
237
+ <span class="font-label-caps text-label-caps text-on-surface-variant uppercase">Pipeline</span>
238
+ </div>
239
+ <div class="pl-4 md:pl-8 border-l-2 border-primary-container flex flex-col gap-1">
240
+ <span class="font-headline-md text-headline-md text-on-surface">&lt; 1s</span>
241
+ <span class="font-label-caps text-label-caps text-on-surface-variant uppercase">Inference</span>
242
+ </div>
243
+ </div>
244
+ </section>
245
+ <!-- 4. Features Bento Grid -->
246
+ <section id="section-features" class="py-20 px-margin-mobile md:px-margin-desktop max-w-container-max mx-auto scroll-mt-20">
247
+ <div class="mb-12">
248
+ <h2 class="font-headline-md text-[28px] text-on-surface mb-2">Technical Capabilities</h2>
249
+ <p class="font-body-main text-on-surface-variant">Built for precision telemetry analysis and race strategy formulation.</p>
250
+ </div>
251
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
252
+ <div class="bg-surface-container border border-surface-variant rounded-lg p-6 hover:border-secondary-container transition-colors group">
253
+ <span class="material-symbols-outlined text-secondary-fixed-dim mb-4 text-[32px] group-hover:scale-110 transition-transform">monitoring</span>
254
+ <h3 class="font-body-fixed text-[16px] text-on-surface mb-2 font-bold">Race Prediction</h3>
255
+ <p class="font-body-main text-[14px] text-on-surface-variant leading-relaxed">Real-time probability updates as track conditions shift.</p>
256
+ </div>
257
+ <div class="bg-surface-container border border-surface-variant rounded-lg p-6 hover:border-primary-container transition-colors group">
258
+ <span class="material-symbols-outlined text-primary-container mb-4 text-[32px] group-hover:scale-110 transition-transform">insights</span>
259
+ <h3 class="font-body-fixed text-[16px] text-on-surface mb-2 font-bold">SHAP Explainability</h3>
260
+ <p class="font-body-main text-[14px] text-on-surface-variant leading-relaxed">Understand the 'why' behind every model output.</p>
261
+ </div>
262
+ <div class="bg-surface-container border border-surface-variant rounded-lg p-6 hover:border-secondary-container transition-colors group">
263
+ <span class="material-symbols-outlined text-secondary-fixed-dim mb-4 text-[32px] group-hover:scale-110 transition-transform">track_changes</span>
264
+ <h3 class="font-body-fixed text-[16px] text-on-surface mb-2 font-bold">Drift Monitoring</h3>
265
+ <p class="font-body-main text-[14px] text-on-surface-variant leading-relaxed">Live validation of model performance against track telemetry.</p>
266
+ </div>
267
+ <div class="bg-surface-container border border-surface-variant rounded-lg p-6 hover:border-primary-container transition-colors group">
268
+ <span class="material-symbols-outlined text-primary-container mb-4 text-[32px] group-hover:scale-110 transition-transform">record_voice_over</span>
269
+ <h3 class="font-body-fixed text-[16px] text-on-surface mb-2 font-bold">Agentic Race Radio</h3>
270
+ <p class="font-body-main text-[14px] text-on-surface-variant leading-relaxed">LLM-synthesized strategic insights from multi-agent consensus.</p>
271
+ </div>
272
+ </div>
273
+ </section>
274
+ <!-- 5. Architecture Band -->
275
+ <section id="section-architecture" class="py-16 bg-surface-container-lowest border-y border-outline-variant/20 overflow-x-auto custom-scrollbar scroll-mt-20">
276
+ <div class="px-margin-mobile md:px-margin-desktop min-w-[800px] max-w-container-max mx-auto flex flex-col items-center">
277
+ <h2 class="font-label-caps text-label-caps text-on-surface-variant uppercase mb-12 tracking-[0.2em]">Inference Architecture</h2>
278
+ <div class="flex items-center gap-2"><div class="group flex flex-col items-center gap-2"><div class="px-4 py-3 bg-surface-container-high border border-outline-variant rounded-lg flex flex-col items-center justify-center min-w-[160px] hover:border-secondary-fixed-dim transition-colors cursor-default"><span class="text-[9px] font-label-caps text-secondary-fixed-dim mb-1 uppercase tracking-widest">Ingesting</span><span class="font-body-fixed text-[12px] text-on-surface text-center">FastF1 + Jolpica</span></div></div><div class="flex items-center"><div class="h-px w-4 bg-secondary-fixed-dim/30"></div><div class="h-px w-8 bg-secondary-fixed-dim flow-line"></div><span class="material-symbols-outlined text-secondary-fixed-dim text-[16px]">chevron_right</span></div><div class="group flex flex-col items-center gap-2"><div class="px-4 py-3 bg-surface-container-high border border-outline-variant rounded-lg flex flex-col items-center justify-center min-w-[160px] hover:border-secondary-fixed-dim transition-colors cursor-default"><span class="text-[9px] font-label-caps text-secondary-fixed-dim mb-1 uppercase tracking-widest">Normalized</span><span class="font-body-fixed text-[12px] text-on-surface text-center">25+ features</span></div></div><div class="flex items-center"><div class="h-px w-4 bg-secondary-fixed-dim/30"></div><div class="h-px w-8 bg-secondary-fixed-dim flow-line" style="animation-delay: 0.5s"></div><span class="material-symbols-outlined text-secondary-fixed-dim text-[16px]">chevron_right</span></div><div class="group flex flex-col items-center gap-2"><div class="px-4 py-3 bg-surface-container-high border border-primary-container rounded-lg flex flex-col items-center justify-center min-w-[160px] shadow-[0_0_10px_rgba(255,85,61,0.2)] hover:border-primary transition-colors cursor-default"><span class="text-[9px] font-label-caps text-primary-container mb-1 uppercase tracking-widest">Trained</span><span class="font-body-fixed text-[12px] text-primary-container font-bold text-center">LightGBM + SHAP</span></div></div><div class="flex items-center"><div class="h-px w-4 bg-secondary-fixed-dim/30"></div><div class="h-px w-8 bg-secondary-fixed-dim flow-line" style="animation-delay: 1s"></div><span class="material-symbols-outlined text-secondary-fixed-dim text-[16px]">chevron_right</span></div><div class="group flex flex-col items-center gap-2"><div class="px-4 py-3 bg-surface-container-high border border-outline-variant rounded-lg flex flex-col items-center justify-center min-w-[160px] hover:border-secondary-fixed-dim transition-colors cursor-default"><span class="text-[9px] font-label-caps text-secondary-fixed-dim mb-1 uppercase tracking-widest">Serving</span><span class="font-body-fixed text-[12px] text-on-surface text-center">FastAPI p95 &lt; 1s</span></div></div><div class="flex items-center"><div class="h-px w-4 bg-secondary-fixed-dim/30"></div><div class="h-px w-8 bg-secondary-fixed-dim flow-line" style="animation-delay: 1.5s"></div><span class="material-symbols-outlined text-secondary-fixed-dim text-[16px]">chevron_right</span></div><div class="group flex flex-col items-center gap-2"><div class="px-4 py-3 bg-surface-container-high border border-outline-variant rounded-lg flex flex-col items-center justify-center min-w-[160px] hover:border-secondary-fixed-dim transition-colors cursor-default"><span class="text-[9px] font-label-caps text-secondary-fixed-dim mb-1 uppercase tracking-widest">Synthesized</span><span class="font-body-fixed text-[12px] text-on-surface text-center">4-agent chain</span></div></div></div>
279
+ </div>
280
+ </section>
281
+ <!-- 6. Final CTA -->
282
+ <section id="section-cta" class="py-24 px-margin-mobile md:px-margin-desktop text-center relative scroll-mt-20">
283
+ <div class="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-surface-container-high/30 via-surface-dim to-surface-dim pointer-events-none"></div>
284
+ <div class="relative z-10 max-w-2xl mx-auto flex flex-col items-center">
285
+ <span class="material-symbols-outlined text-secondary-fixed-dim text-[48px] mb-6">flag</span>
286
+ <h2 class="font-display-lg text-display-lg-mobile md:text-[40px] text-on-surface mb-8">Ask the pit wall before race day</h2>
287
+ <div class="flex flex-col sm:flex-row gap-4 justify-center">
288
+ <button id="cta-launch-btn" class="bg-primary-container text-on-primary-container px-8 py-3 rounded hover:bg-primary/90 transition-colors font-headline-md text-[16px]">Launch predictor</button>
289
+ <button id="cta-docs-btn" class="border border-outline-variant text-on-surface px-8 py-3 rounded hover:bg-surface-container transition-colors font-body-fixed text-[14px]">Read API docs</button>
290
+ </div>
291
+ </div>
292
+ </section>
293
+ </main>
294
+ <!-- 7. Footer -->
295
+ <footer class="bg-surface-container-lowest full-width py-12 px-margin-desktop border-t border-outline-variant/20 flex flex-col md:flex-row justify-between items-start gap-gutter mt-auto">
296
+ <div class="flex flex-col gap-2">
297
+ <span class="font-display-lg text-display-lg-mobile uppercase text-on-surface-variant opacity-50 md:text-[24px]">KRONECTOR</span>
298
+ <p class="font-label-caps text-label-caps text-secondary-fixed-dim">KRONECTOR INTELLIGENCE. ALL SYSTEMS OPERATIONAL. © 2024</p>
299
+ </div>
300
+ <nav class="flex flex-wrap gap-6 mt-6 md:mt-0">
301
+ <a id="footer-api-ref" class="font-body-fixed text-body-fixed text-on-surface-variant hover:text-primary transition-opacity cursor-pointer">API Reference</a>
302
+ <a id="footer-docs" class="font-body-fixed text-body-fixed text-on-surface-variant hover:text-primary transition-opacity cursor-pointer">Documentation</a>
303
+ <a id="footer-status" class="font-body-fixed text-body-fixed text-on-surface-variant hover:text-primary transition-opacity cursor-pointer">Status</a>
304
+ <a class="font-body-fixed text-body-fixed text-on-surface-variant hover:text-primary transition-opacity" href="#">Terms</a>
305
+ </nav>
306
+ </footer>
307
+ <!-- Toast Notification -->
308
+ <div id="toast" class="fixed bottom-6 right-6 z-[100] px-5 py-3 rounded-lg font-body-fixed text-[13px] shadow-2xl border transition-all duration-300 opacity-0 translate-y-4 pointer-events-none" style="background:#201f1f;border-color:#5f3e39;color:#e5e2e1;"></div>
309
+
310
+ <script>
311
+ // ============================================================================
312
+ // KRONECTOR Frontend — API Integration
313
+ // ============================================================================
314
+
315
+ const API_BASE = 'http://localhost:8000';
316
+
317
+ // --- Utility ---
318
+ function $(id) { return document.getElementById(id); }
319
+
320
+ function showToast(msg, type = 'info') {
321
+ const t = $('toast');
322
+ t.textContent = msg;
323
+ t.style.borderColor = type === 'error' ? '#93000a' : type === 'success' ? '#00dbe7' : '#5f3e39';
324
+ t.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
325
+ t.classList.add('opacity-100', 'translate-y-0');
326
+ clearTimeout(t._timer);
327
+ t._timer = setTimeout(() => {
328
+ t.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
329
+ t.classList.remove('opacity-100', 'translate-y-0');
330
+ }, 4000);
331
+ }
332
+
333
+ function scrollToInput() {
334
+ const input = $('terminal-input');
335
+ input.scrollIntoView({ behavior: 'smooth', block: 'center' });
336
+ setTimeout(() => input.focus(), 400);
337
+ }
338
+
339
+ // --- Smooth Scroll for nav links ---
340
+ document.querySelectorAll('a[href^="#section-"]').forEach(a => {
341
+ a.addEventListener('click', e => {
342
+ e.preventDefault();
343
+ const target = document.querySelector(a.getAttribute('href'));
344
+ if (target) target.scrollIntoView({ behavior: 'smooth' });
345
+ });
346
+ });
347
+
348
+ // --- Health Check ---
349
+ async function checkHealth() {
350
+ const dot = $('health-dot');
351
+ const label = $('health-label');
352
+ try {
353
+ const res = await fetch(`${API_BASE}/health`);
354
+ const data = await res.json();
355
+ if (data.status === 'healthy') {
356
+ dot.className = 'w-2 h-2 rounded-full bg-secondary-fixed-dim animate-pulse';
357
+ label.textContent = 'Live';
358
+ label.className = 'font-label-caps text-label-caps text-secondary-fixed-dim uppercase';
359
+ } else {
360
+ dot.className = 'w-2 h-2 rounded-full bg-primary-container animate-pulse';
361
+ label.textContent = 'Degraded';
362
+ label.className = 'font-label-caps text-label-caps text-primary-container uppercase';
363
+ }
364
+ } catch {
365
+ dot.className = 'w-2 h-2 rounded-full bg-error';
366
+ dot.classList.remove('animate-pulse');
367
+ label.textContent = 'Offline';
368
+ label.className = 'font-label-caps text-label-caps text-error uppercase';
369
+ }
370
+ }
371
+
372
+ // --- Prediction API ---
373
+ let _isLoading = false;
374
+
375
+ async function runPrediction(query) {
376
+ if (_isLoading) return;
377
+ if (!query || query.trim().length < 3) {
378
+ showToast('Query too short. Try something like: "Who wins the 2024 Monaco GP?"', 'error');
379
+ return;
380
+ }
381
+ _isLoading = true;
382
+ const sendBtn = $('terminal-send');
383
+ sendBtn.disabled = true;
384
+
385
+ // Show loading state in terminal
386
+ $('terminal-gp-label').textContent = 'Processing...';
387
+ $('terminal-confidence').textContent = 'Inference Running';
388
+ $('prediction-content').innerHTML = '<p class="font-body-fixed text-[12px] text-secondary-fixed-dim animate-pulse">⏳ Querying model pipeline...</p>';
389
+ $('shap-content').innerHTML = '<p class="text-on-surface-variant/60 italic text-[11px] animate-pulse">Computing SHAP values...</p>';
390
+ $('radio-content').innerHTML = '<div class="animate-pulse"><span class="text-secondary-container">[STRAT_AGENT]</span> Synthesizing response...</div>';
391
+
392
+ try {
393
+ const res = await fetch(`${API_BASE}/predict/f1`, {
394
+ method: 'POST',
395
+ headers: { 'Content-Type': 'application/json' },
396
+ body: JSON.stringify({ query: query.trim() })
397
+ });
398
+
399
+ if (!res.ok) {
400
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
401
+ throw new Error(err.detail || `API Error ${res.status}`);
402
+ }
403
+
404
+ const data = await res.json();
405
+ renderPrediction(data);
406
+ showToast('Prediction complete ✓', 'success');
407
+ } catch (err) {
408
+ $('prediction-content').innerHTML = `<p class="font-body-fixed text-[12px] text-error">❌ ${err.message}</p>`;
409
+ $('shap-content').innerHTML = '<p class="text-error text-[11px]">Error</p>';
410
+ $('radio-content').innerHTML = `<div><span class="text-error">[ERROR]</span> ${err.message}</div>`;
411
+ $('terminal-gp-label').textContent = 'Error';
412
+ $('terminal-confidence').textContent = '';
413
+ showToast(err.message, 'error');
414
+ } finally {
415
+ _isLoading = false;
416
+ sendBtn.disabled = false;
417
+ }
418
+ }
419
+
420
+ function renderPrediction(data) {
421
+ const m = data.metadata;
422
+
423
+ // Terminal header
424
+ $('terminal-confidence').textContent = data.confidence_rating
425
+ ? `Confidence: ${data.confidence_rating}`
426
+ : `Win: ${data.win_probability}%`;
427
+ $('terminal-gp-label').textContent = `${m.season} Round ${m.round} — ${m.driver_name}`;
428
+
429
+ // Prediction pane — single driver result card
430
+ const probColor = data.win_probability > 50 ? 'text-primary-container' : data.win_probability > 20 ? 'text-secondary-fixed-dim' : 'text-on-surface-variant';
431
+ $('prediction-content').innerHTML = `
432
+ <table class="w-full text-left font-body-fixed text-[11px] border-separate border-spacing-y-1">
433
+ <thead class="text-on-surface-variant uppercase text-[9px] tracking-wider">
434
+ <tr>
435
+ <th class="px-2 py-1">Driver</th>
436
+ <th class="px-2 py-1">Team</th>
437
+ <th class="px-2 py-1">Grid</th>
438
+ <th class="px-2 py-1">Season</th>
439
+ <th class="px-2 py-1">Round</th>
440
+ <th class="px-2 py-1 text-right">Win %</th>
441
+ </tr>
442
+ </thead>
443
+ <tbody>
444
+ <tr class="bg-surface-container-low rounded">
445
+ <td class="px-2 py-1.5 text-on-surface font-bold">${m.driver_name}</td>
446
+ <td class="px-2 py-1.5">${m.team}</td>
447
+ <td class="px-2 py-1.5">P${m.grid_position}</td>
448
+ <td class="px-2 py-1.5">${m.season}</td>
449
+ <td class="px-2 py-1.5">${m.round}</td>
450
+ <td class="px-2 py-1.5 text-right ${probColor} font-bold text-[14px]">${data.win_probability}%</td>
451
+ </tr>
452
+ </tbody>
453
+ </table>
454
+ `;
455
+
456
+ // SHAP pane — top features as horizontal bars
457
+ const shapEl = $('shap-content');
458
+ if (data.shap_values && Object.keys(data.shap_values).length > 0) {
459
+ const entries = Object.entries(data.shap_values)
460
+ .map(([k, v]) => [k, parseFloat(v)])
461
+ .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
462
+ .slice(0, 6);
463
+ const maxAbs = Math.max(...entries.map(e => Math.abs(e[1])), 0.001);
464
+
465
+ shapEl.innerHTML = entries.map(([feat, val]) => {
466
+ const pct = Math.min(Math.abs(val) / maxAbs * 100, 100).toFixed(0);
467
+ const color = val >= 0 ? 'bg-primary-container' : 'bg-secondary-fixed-dim';
468
+ const textColor = val >= 0 ? 'text-primary-container' : 'text-secondary-fixed-dim';
469
+ const dir = val >= 0 ? 'right-0' : 'left-0';
470
+ return `
471
+ <div class="flex items-center gap-3">
472
+ <span class="font-label-caps text-[9px] w-24 text-on-surface-variant/80 truncate" title="${feat}">${feat.replace(/_/g, ' ').toUpperCase()}</span>
473
+ <div class="flex-1 h-2 bg-surface-variant rounded-sm relative">
474
+ <div class="absolute ${dir} top-0 h-full ${color} rounded-sm shap-bar" style="--target-width:${pct}%;width:${pct}%"></div>
475
+ </div>
476
+ <span class="font-label-caps text-[9px] ${textColor} w-12 text-right">${val >= 0 ? '+' : ''}${val.toFixed(3)}</span>
477
+ </div>
478
+ `;
479
+ }).join('');
480
+ } else {
481
+ shapEl.innerHTML = '<p class="text-on-surface-variant/60 italic text-[11px]">No SHAP data available</p>';
482
+ }
483
+
484
+ // Race Radio pane — LLM explanation
485
+ const radioEl = $('radio-content');
486
+ if (data.llm_explanation) {
487
+ const now = new Date();
488
+ const ts = `${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}:${now.getSeconds().toString().padStart(2,'0')}`;
489
+ // Split explanation into lines for radio-style formatting
490
+ const lines = data.llm_explanation.split(/\n+/).filter(l => l.trim());
491
+ radioEl.innerHTML = lines.map((line, i) => {
492
+ const tag = i === 0 ? '[SYNTHESIS]' : '[STRAT_AGENT]';
493
+ const tagColor = i === 0 ? 'text-secondary-container' : 'text-secondary-fixed-dim';
494
+ return `<div><span class="text-on-surface-variant">${ts}</span> <span class="${tagColor}">${tag}</span> ${line.trim()}</div>`;
495
+ }).join('');
496
+ } else {
497
+ radioEl.innerHTML = '<div><span class="text-on-surface-variant/50">—</span> No synthesis available</div>';
498
+ }
499
+ }
500
+
501
+ // --- Button Wiring ---
502
+
503
+ // "Run Prediction" buttons → scroll to terminal input
504
+ $('nav-run-btn').addEventListener('click', scrollToInput);
505
+ $('hero-run-btn').addEventListener('click', scrollToInput);
506
+ $('cta-launch-btn').addEventListener('click', scrollToInput);
507
+
508
+ // "View model proof" → scroll to features section
509
+ $('hero-proof-btn').addEventListener('click', () => {
510
+ document.querySelector('#section-features').scrollIntoView({ behavior: 'smooth' });
511
+ });
512
+
513
+ // "Read API docs" → open Swagger
514
+ $('cta-docs-btn').addEventListener('click', () => {
515
+ window.open(`${API_BASE}/docs`, '_blank');
516
+ });
517
+
518
+ // Terminal send button + Enter key
519
+ $('terminal-send').addEventListener('click', () => {
520
+ runPrediction($('terminal-input').value);
521
+ });
522
+ $('terminal-input').addEventListener('keydown', (e) => {
523
+ if (e.key === 'Enter') runPrediction($('terminal-input').value);
524
+ });
525
+
526
+ // Health indicator click → re-check
527
+ $('health-indicator').addEventListener('click', () => {
528
+ checkHealth();
529
+ showToast('Health check triggered', 'info');
530
+ });
531
+
532
+ // Footer links
533
+ $('footer-api-ref').addEventListener('click', () => window.open(`${API_BASE}/docs`, '_blank'));
534
+ $('footer-docs').addEventListener('click', () => window.open(`${API_BASE}/redoc`, '_blank'));
535
+ $('footer-status').addEventListener('click', async () => {
536
+ try {
537
+ const res = await fetch(`${API_BASE}/health`);
538
+ const data = await res.json();
539
+ showToast(`Status: ${data.status} | Model: ${data.model_loaded ? '✓' : '✗'} | Data: ${data.data_available ? '✓' : '✗'}`, data.status === 'healthy' ? 'success' : 'error');
540
+ } catch {
541
+ showToast('API unreachable', 'error');
542
+ }
543
+ });
544
+
545
+ // --- Init ---
546
+ checkHealth();
547
+ // Re-check health every 30s
548
+ setInterval(checkHealth, 30000);
549
+ </script>
550
+ </body></html>
frontend/eslint.config.mjs DELETED
@@ -1,16 +0,0 @@
1
- import { defineConfig, globalIgnores } from "eslint/config";
2
- import nextVitals from "eslint-config-next/core-web-vitals";
3
-
4
- const eslintConfig = defineConfig([
5
- ...nextVitals,
6
- // Override default ignores of eslint-config-next.
7
- globalIgnores([
8
- // Default ignores of eslint-config-next:
9
- ".next/**",
10
- "out/**",
11
- "build/**",
12
- "next-env.d.ts",
13
- ]),
14
- ]);
15
-
16
- export default eslintConfig;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/hooks/use-auto-resize-textarea.ts DELETED
@@ -1,57 +0,0 @@
1
- import { useEffect, useRef, useCallback } from "react";
2
-
3
- interface UseAutoResizeTextareaProps {
4
- minHeight: number;
5
- maxHeight?: number;
6
- }
7
-
8
- export function useAutoResizeTextarea({
9
- minHeight,
10
- maxHeight,
11
- }: UseAutoResizeTextareaProps) {
12
- const textareaRef = useRef<HTMLTextAreaElement>(null);
13
-
14
- const adjustHeight = useCallback(
15
- (reset?: boolean) => {
16
- const textarea = textareaRef.current;
17
- if (!textarea) return;
18
-
19
- if (reset) {
20
- textarea.style.height = `${minHeight}px`;
21
- return;
22
- }
23
-
24
- // Temporarily shrink to get the right scrollHeight
25
- textarea.style.height = `${minHeight}px`;
26
-
27
- // Calculate new height
28
- const newHeight = Math.max(
29
- minHeight,
30
- Math.min(
31
- textarea.scrollHeight,
32
- maxHeight ?? Number.POSITIVE_INFINITY
33
- )
34
- );
35
-
36
- textarea.style.height = `${newHeight}px`;
37
- },
38
- [minHeight, maxHeight]
39
- );
40
-
41
- useEffect(() => {
42
- // Set initial height
43
- const textarea = textareaRef.current;
44
- if (textarea) {
45
- textarea.style.height = `${minHeight}px`;
46
- }
47
- }, [minHeight]);
48
-
49
- // Adjust height on window resize
50
- useEffect(() => {
51
- const handleResize = () => adjustHeight();
52
- window.addEventListener("resize", handleResize);
53
- return () => window.removeEventListener("resize", handleResize);
54
- }, [adjustHeight]);
55
-
56
- return { textareaRef, adjustHeight };
57
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/index.html ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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" />
6
+ <title>Kronector | F1 Intelligence</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ min-height: 100vh;
11
+ display: grid;
12
+ place-items: center;
13
+ background: #0a0a0a;
14
+ color: #f4f4f4;
15
+ font-family: 'Inter', Arial, sans-serif;
16
+ }
17
+ a { color: #00f2ff; }
18
+ </style>
19
+ <script>
20
+ // Auto-redirect based on viewport width
21
+ (function() {
22
+ var target = window.innerWidth <= 768 ? 'mobile.html' : 'desktop.html';
23
+ window.location.replace(target);
24
+ })();
25
+ </script>
26
+ </head>
27
+ <body>
28
+ <main>
29
+ <p>Opening Kronector...</p>
30
+ <p><a href="desktop.html">Desktop</a> · <a href="mobile.html">Mobile</a></p>
31
+ </main>
32
+ </body>
33
+ </html>
frontend/jsconfig.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "paths": {
4
- "@/*": ["./*"]
5
- }
6
- }
7
- }
 
 
 
 
 
 
 
 
frontend/lib/utils.ts DELETED
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
 
 
 
 
 
 
 
frontend/mobile.html ADDED
@@ -0,0 +1,541 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html class="dark" lang="en"><head>
4
+ <meta charset="utf-8"/>
5
+ <meta content="width=device-width, initial-scale=1.0, viewport-fit=cover" name="viewport"/>
6
+ <title>KRONECTOR | F1 Intelligence</title>
7
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700;800&amp;family=JetBrains+Mono:wght@400;700&amp;display=swap" rel="stylesheet"/>
9
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
10
+ <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
11
+ <script id="tailwind-config">
12
+ tailwind.config = {
13
+ darkMode: "class",
14
+ theme: {
15
+ extend: {
16
+ "colors": {
17
+ "surface": "#131313",
18
+ "primary": "#ffb4a7",
19
+ "on-surface-variant": "#eabcb4",
20
+ "on-primary-container": "#5b0300",
21
+ "secondary-fixed": "#74f5ff",
22
+ "primary-fixed-dim": "#ffb4a7",
23
+ "surface-tint": "#ffb4a7",
24
+ "secondary": "#ddfcff",
25
+ "secondary-fixed-dim": "#00dbe7",
26
+ "on-tertiary-container": "#282a2a",
27
+ "on-surface": "#e5e2e1",
28
+ "surface-container-lowest": "#0e0e0e",
29
+ "on-secondary-container": "#006a70",
30
+ "inverse-primary": "#be0e00",
31
+ "on-tertiary-fixed-variant": "#454747",
32
+ "on-secondary-fixed": "#002022",
33
+ "background": "#131313",
34
+ "secondary-container": "#00f1fe",
35
+ "on-primary-fixed": "#400200",
36
+ "surface-container-high": "#2a2a2a",
37
+ "surface-container-highest": "#353534",
38
+ "tertiary-fixed-dim": "#c6c6c7",
39
+ "primary-container": "#ff553d",
40
+ "on-background": "#e5e2e1",
41
+ "surface-container": "#201f1f",
42
+ "error": "#ffb4ab",
43
+ "surface-dim": "#131313",
44
+ "surface-bright": "#3a3939",
45
+ "on-primary": "#670400",
46
+ "on-secondary-fixed-variant": "#004f54",
47
+ "tertiary-fixed": "#e2e2e2",
48
+ "tertiary": "#c6c6c7",
49
+ "surface-variant": "#353534",
50
+ "on-tertiary-fixed": "#1a1c1c",
51
+ "on-primary-fixed-variant": "#910900",
52
+ "surface-container-low": "#1c1b1b",
53
+ "primary-fixed": "#ffdad4",
54
+ "outline-variant": "#5f3e39",
55
+ "error-container": "#93000a",
56
+ "tertiary-container": "#909191",
57
+ "on-tertiary": "#2f3131",
58
+ "inverse-surface": "#e5e2e1",
59
+ "inverse-on-surface": "#313030",
60
+ "outline": "#b08780",
61
+ "on-error": "#690005",
62
+ "on-error-container": "#ffdad6",
63
+ "on-secondary": "#00363a",
64
+ "race-red": "#ff1801",
65
+ "neon-cyan": "#00dbe7"
66
+ },
67
+ "borderRadius": {
68
+ "DEFAULT": "0.25rem",
69
+ "lg": "0.5rem",
70
+ "xl": "0.75rem",
71
+ "full": "9999px"
72
+ },
73
+ "spacing": {
74
+ "container-max": "1440px",
75
+ "unit": "4px",
76
+ "margin-desktop": "32px",
77
+ "margin-mobile": "16px",
78
+ "gutter": "16px"
79
+ },
80
+ "fontFamily": {
81
+ "headline-md": ["Inter"],
82
+ "label-caps": ["JetBrains Mono"],
83
+ "display-lg-mobile": ["Inter"],
84
+ "body-fixed": ["JetBrains Mono"],
85
+ "display-lg": ["Inter"],
86
+ "body-main": ["Inter"]
87
+ },
88
+ "fontSize": {
89
+ "headline-md": ["24px", {"lineHeight": "1.3", "letterSpacing": "-0.01em", "fontWeight": "700"}],
90
+ "label-caps": ["11px", {"lineHeight": "1", "letterSpacing": "0.1em", "fontWeight": "700"}],
91
+ "display-lg-mobile": ["32px", {"lineHeight": "1.2", "letterSpacing": "-0.02em", "fontWeight": "800"}],
92
+ "body-fixed": ["14px", {"lineHeight": "1.5", "letterSpacing": "0em", "fontWeight": "400"}],
93
+ "display-lg": ["48px", {"lineHeight": "1.1", "letterSpacing": "-0.04em", "fontWeight": "800"}],
94
+ "body-main": ["16px", {"lineHeight": "1.6", "letterSpacing": "0em", "fontWeight": "400"}]
95
+ }
96
+ },
97
+ },
98
+ }
99
+ </script>
100
+ <style>
101
+ body { background-color: #0A0A0A; color: #e5e2e1; }
102
+ .carbon-border { border: 1px solid #2D2D2D; }
103
+ .metric-accent-red { border-left: 2px solid #ff1801; }
104
+ .metric-accent-cyan { border-left: 2px solid #00dbe7; }
105
+ .terminal-header { background: linear-gradient(to bottom, #1A1A1A, #111111); }
106
+ .zebra-list div:nth-child(even) { background-color: #151515; }
107
+ .pulse-node { animation: pulse 2s infinite; }
108
+ @keyframes pulse { 0% { opacity: 0.5; } 50% { opacity: 1; } 100% { opacity: 0.5; } }
109
+ </style>
110
+ <style>
111
+ body {
112
+ min-height: max(884px, 100dvh);
113
+ }
114
+ </style>
115
+ </head>
116
+ <body class="font-body-main selection:bg-race-red selection:text-white overflow-x-hidden">
117
+ <!-- TopAppBar -->
118
+ <header class="fixed top-0 w-full z-50 backdrop-blur-md bg-surface/80 border-b border-outline-variant flex justify-between items-center px-margin-mobile h-14 w-full">
119
+ <div class="flex items-center gap-3">
120
+ <span class="material-symbols-outlined text-primary" data-icon="menu">menu</span>
121
+ <span class="font-headline-md text-headline-md-mobile font-black tracking-tighter text-primary">KRONECTOR</span>
122
+ </div>
123
+ <div class="w-8 h-8 rounded-full overflow-hidden border border-outline-variant">
124
+ <img alt="Engineer Profile" class="w-full h-full object-cover" data-alt="A professional headshot of a technical Formula 1 race engineer wearing high-end headphones in a dark, data-driven pit wall environment. The lighting is dominated by glowing cyan telemetry screens reflecting off their glasses, creating a sharp, high-performance motorsport atmosphere with a focus on deep blacks and vibrant technical accents." src="https://lh3.googleusercontent.com/aida-public/AB6AXuDdxk5F6y5JOwsj3vJFZHGg2ZIMdMP5AxXvxwGJhvFasyBsskV0kwijwXRDP9uIJGM4j_RLw-gxyg_3JVGPtd6ajb3-RhxilueEqcohiFz4dlFkeudrBbfZSsCrIHVyym6UUDf-9fYc4f1xuCRZD5mzdJKRwrJW128xlvWZaJ5Gc_-oM8ZsFJR0iedjp17YpWlMghL-7gr8sIKG6dZc8KknBJG3ZkiLE37hUyoCjHcRAAl6ZSJne9nMJADed0fFMCrTlk6bGdUsSqi8"/>
125
+ </div>
126
+ </header>
127
+ <main class="pt-20 pb-24 px-margin-mobile flex flex-col gap-10">
128
+ <!-- Hero Section -->
129
+ <section id="m-section-hero" class="flex flex-col gap-6">
130
+ <div class="space-y-4">
131
+ <h1 class="font-display-lg-mobile text-display-lg-mobile leading-tight">
132
+ Predict the race before <span class="text-race-red">lights out</span>
133
+ </h1>
134
+ <p class="font-body-main text-on-surface-variant leading-relaxed">
135
+ Kronector turns Formula 1 telemetry, qualifying pace, driver form, and model explainability into race-winning predictions before the grid forms.
136
+ </p>
137
+ </div>
138
+ <div class="flex flex-col gap-3">
139
+ <button id="m-hero-run-btn" class="bg-race-red text-white font-headline-md py-4 rounded-lg flex items-center justify-center gap-2 active:scale-95 transition-transform">
140
+ Run prediction
141
+ </button>
142
+ <button id="m-hero-proof-btn" class="border border-neon-cyan text-neon-cyan font-label-caps py-4 rounded-lg flex items-center justify-center gap-2 uppercase tracking-widest active:scale-95 transition-transform">
143
+ View model proof
144
+ </button>
145
+ </div>
146
+ </section>
147
+ <!-- Prediction Terminal -->
148
+ <section id="m-section-terminal" class="carbon-border rounded-lg bg-[#111111] overflow-hidden flex flex-col scroll-mt-20">
149
+ <div class="terminal-header p-3 border-b border-[#2D2D2D] flex justify-between items-center">
150
+ <div class="flex gap-1.5">
151
+ <div class="w-2.5 h-2.5 rounded-full bg-[#333]"></div>
152
+ <div class="w-2.5 h-2.5 rounded-full bg-[#333]"></div>
153
+ <div class="w-2.5 h-2.5 rounded-full bg-[#333]"></div>
154
+ </div>
155
+ <span id="m-terminal-label" class="font-label-caps text-[10px] text-on-surface-variant/60 uppercase">READY FOR QUERY</span>
156
+ </div>
157
+ <!-- Conditions Bar -->
158
+ <div id="m-conditions-bar" class="grid grid-cols-3 divide-x divide-[#2D2D2D] border-b border-[#2D2D2D]">
159
+ <div class="p-3 flex flex-col gap-1">
160
+ <span class="font-label-caps text-[9px] text-on-surface-variant/50">DRIVER</span>
161
+ <span id="m-cond-driver" class="font-body-fixed text-neon-cyan">—</span>
162
+ </div>
163
+ <div class="p-3 flex flex-col gap-1">
164
+ <span class="font-label-caps text-[9px] text-on-surface-variant/50">TEAM</span>
165
+ <span id="m-cond-team" class="font-body-fixed text-on-surface">—</span>
166
+ </div>
167
+ <div class="p-3 flex flex-col gap-1">
168
+ <span class="font-label-caps text-[9px] text-on-surface-variant/50">WIN %</span>
169
+ <span id="m-cond-prob" class="font-body-fixed text-race-red">—</span>
170
+ </div>
171
+ </div>
172
+ <!-- Driver List -->
173
+ <div id="m-driver-list" class="zebra-list flex flex-col">
174
+ <div class="p-4 flex items-center justify-center">
175
+ <p class="font-body-fixed text-[12px] text-on-surface-variant/60 italic">Submit a query to see predictions...</p>
176
+ </div>
177
+ </div>
178
+ <!-- SHAP Bars -->
179
+ <div id="m-shap-pane" class="p-4 bg-[#0D0D0D] border-t border-[#2D2D2D] flex flex-col gap-3">
180
+ <span class="font-label-caps text-[10px] text-on-surface-variant/60">SHAP FEATURE INFLUENCE</span>
181
+ <div id="m-shap-content" class="space-y-2">
182
+ <p class="font-body-fixed text-[11px] text-on-surface-variant/50 italic">Awaiting prediction...</p>
183
+ </div>
184
+ </div>
185
+ <!-- Radio Feed -->
186
+ <div id="m-radio-pane" class="p-4 bg-[#080808] border-t border-[#2D2D2D]">
187
+ <div class="flex items-center gap-2 mb-2">
188
+ <span class="material-symbols-outlined text-[14px] text-race-red" data-icon="settings_input_antenna">settings_input_antenna</span>
189
+ <span class="font-label-caps text-[10px] text-race-red">LATEST SYNTHESIS</span>
190
+ </div>
191
+ <p id="m-radio-content" class="font-body-fixed text-[12px] text-on-surface/90 italic leading-relaxed">
192
+ Waiting for agentic synthesis...
193
+ </p>
194
+ </div>
195
+ <!-- Input Bar -->
196
+ <div class="p-3 bg-[#1A1A1A] border-t border-[#2D2D2D] flex gap-2 items-center">
197
+ <input id="m-terminal-input" class="flex-1 bg-black border border-[#2D2D2D] rounded-lg px-3 py-2 text-xs font-body-fixed focus:border-neon-cyan focus:ring-0" placeholder="Who wins the 2026 Canadian GP?" type="text" autocomplete="off"/>
198
+ <button id="m-terminal-send" class="w-8 h-8 flex items-center justify-center bg-race-red rounded-lg">
199
+ <span class="material-symbols-outlined text-white text-sm" data-icon="send">send</span>
200
+ </button>
201
+ </div>
202
+ </section>
203
+ <!-- Metrics Grid -->
204
+ <section id="m-section-metrics" class="grid grid-cols-2 gap-4 scroll-mt-20">
205
+ <div class="carbon-border rounded-lg p-4 metric-accent-cyan flex flex-col gap-1">
206
+ <span class="font-body-fixed font-bold text-lg">71%</span>
207
+ <span class="font-label-caps text-[9px] text-on-surface-variant/60 uppercase">Winner Accuracy</span>
208
+ </div>
209
+ <div class="carbon-border rounded-lg p-4 metric-accent-red flex flex-col gap-1">
210
+ <span class="font-body-fixed font-bold text-lg">0.94</span>
211
+ <span class="font-label-caps text-[9px] text-on-surface-variant/60 uppercase">ROC AUC</span>
212
+ </div>
213
+ <div class="carbon-border rounded-lg p-4 metric-accent-cyan flex flex-col gap-1">
214
+ <span class="font-body-fixed font-bold text-lg">4-Agent</span>
215
+ <span class="font-label-caps text-[9px] text-on-surface-variant/60 uppercase">Pipeline</span>
216
+ </div>
217
+ <div class="carbon-border rounded-lg p-4 metric-accent-red flex flex-col gap-1">
218
+ <span class="font-body-fixed font-bold text-lg">&lt; 1s</span>
219
+ <span class="font-label-caps text-[9px] text-on-surface-variant/60 uppercase">Inference</span>
220
+ </div>
221
+ </section>
222
+ <!-- Technical Capabilities -->
223
+ <section id="m-section-capabilities" class="flex flex-col gap-4 scroll-mt-20">
224
+ <h2 class="font-label-caps text-on-surface-variant/50 uppercase tracking-widest text-[10px]">Technical Core</h2>
225
+ <div class="space-y-3">
226
+ <div class="carbon-border rounded-lg p-5 flex gap-4 items-start">
227
+ <span class="material-symbols-outlined text-race-red" data-icon="query_stats">query_stats</span>
228
+ <div class="space-y-1">
229
+ <h3 class="font-headline-md text-sm text-on-surface">Race Prediction</h3>
230
+ <p class="font-body-fixed text-xs text-on-surface-variant leading-normal">XGBoost &amp; LSTM hybrid ensemble trained on 20 years of historical race data and live lap telemetry.</p>
231
+ </div>
232
+ </div>
233
+ <div class="carbon-border rounded-lg p-5 flex gap-4 items-start">
234
+ <span class="material-symbols-outlined text-neon-cyan" data-icon="explain">expand</span>
235
+ <div class="space-y-1">
236
+ <h3 class="font-headline-md text-sm text-on-surface">SHAP Explainability</h3>
237
+ <p class="font-body-fixed text-xs text-on-surface-variant leading-normal">Deep-dive into every prediction. Understand exactly how tire age or air pressure influenced the model's output.</p>
238
+ </div>
239
+ </div>
240
+ <div class="carbon-border rounded-lg p-5 flex gap-4 items-start">
241
+ <span class="material-symbols-outlined text-race-red" data-icon="monitoring">monitoring</span>
242
+ <div class="space-y-1">
243
+ <h3 class="font-headline-md text-sm text-on-surface">Drift Monitoring</h3>
244
+ <p class="font-body-fixed text-xs text-on-surface-variant leading-normal">Real-time detection of concept drift as track conditions evolve beyond the initial training baseline.</p>
245
+ </div>
246
+ </div>
247
+ <div class="carbon-border rounded-lg p-5 flex gap-4 items-start">
248
+ <span class="material-symbols-outlined text-neon-cyan" data-icon="speaker_group">speaker_group</span>
249
+ <div class="space-y-1">
250
+ <h3 class="font-headline-md text-sm text-on-surface">Agentic Race Radio</h3>
251
+ <p class="font-body-fixed text-xs text-on-surface-variant leading-normal">LLM-powered reasoning agents synthesize raw model outputs into strategic audio/text directives.</p>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </section>
256
+ <!-- Architecture Flow -->
257
+ <section id="m-section-architecture" class="flex flex-col gap-6 py-4 scroll-mt-20">
258
+ <h2 class="font-label-caps text-on-surface-variant/50 uppercase tracking-widest text-[10px] text-center">Pipeline Architecture</h2>
259
+ <div class="relative flex flex-col items-center gap-12">
260
+ <!-- Vertical Line Background -->
261
+ <div class="absolute h-full w-[1.5px] bg-gradient-to-b from-race-red via-neon-cyan to-race-red opacity-30 z-0"></div>
262
+ <!-- Node 1 -->
263
+ <div class="z-10 bg-surface carbon-border p-4 rounded-lg w-full flex items-center justify-between">
264
+ <div class="flex items-center gap-3">
265
+ <div class="w-2 h-2 rounded-full bg-neon-cyan pulse-node"></div>
266
+ <span class="font-label-caps text-xs">FASTF1 + JOLPICA</span>
267
+ </div>
268
+ <span class="bg-on-secondary-fixed-variant text-secondary-fixed text-[9px] px-2 py-0.5 rounded font-label-caps">INGESTING</span>
269
+ </div>
270
+ <!-- Node 2 -->
271
+ <div class="z-10 bg-surface carbon-border p-4 rounded-lg w-full flex items-center justify-between">
272
+ <div class="flex items-center gap-3">
273
+ <div class="w-2 h-2 rounded-full bg-white/20"></div>
274
+ <span class="font-label-caps text-xs">25+ FEATURE VECTOR</span>
275
+ </div>
276
+ <span class="bg-surface-container-highest text-on-surface-variant text-[9px] px-2 py-0.5 rounded font-label-caps">NORMALIZED</span>
277
+ </div>
278
+ <!-- Node 3 -->
279
+ <div class="z-10 bg-surface carbon-border p-4 rounded-lg w-full flex items-center justify-between">
280
+ <div class="flex items-center gap-3">
281
+ <div class="w-2 h-2 rounded-full bg-race-red pulse-node"></div>
282
+ <span class="font-label-caps text-xs">ENSEMBLE BACKBONE</span>
283
+ </div>
284
+ <span class="bg-error-container text-error text-[9px] px-2 py-0.5 rounded font-label-caps">TRAINED</span>
285
+ </div>
286
+ <!-- Node 4 -->
287
+ <div class="z-10 bg-surface carbon-border p-4 rounded-lg w-full flex items-center justify-between">
288
+ <div class="flex items-center gap-3">
289
+ <div class="w-2 h-2 rounded-full bg-neon-cyan"></div>
290
+ <span class="font-label-caps text-xs">TRITON INFERENCE</span>
291
+ </div>
292
+ <span class="bg-on-secondary-container text-secondary-container text-[9px] px-2 py-0.5 rounded font-label-caps">SERVING</span>
293
+ </div>
294
+ <!-- Node 5 -->
295
+ <div class="z-10 bg-surface carbon-border p-4 rounded-lg w-full flex items-center justify-between">
296
+ <div class="flex items-center gap-3">
297
+ <div class="w-2 h-2 rounded-full bg-white/50"></div>
298
+ <span class="font-label-caps text-xs">LLM PIT-WALL</span>
299
+ </div>
300
+ <span class="bg-surface-container-high text-on-surface text-[9px] px-2 py-0.5 rounded font-label-caps">SYNTHESIZED</span>
301
+ </div>
302
+ </div>
303
+ </section>
304
+ <!-- Final CTA -->
305
+ <section id="m-section-cta" class="bg-surface-container-low rounded-lg p-8 text-center flex flex-col gap-6 border border-outline-variant scroll-mt-20">
306
+ <div class="space-y-2">
307
+ <h2 class="font-headline-md text-display-lg-mobile">Ask the pit wall before race day</h2>
308
+ <p class="font-body-main text-on-surface-variant text-sm">Deploy the most advanced prediction suite in open-source motorsport intelligence.</p>
309
+ </div>
310
+ <div class="flex flex-col gap-3">
311
+ <button id="m-cta-launch-btn" class="bg-race-red text-white font-headline-md py-4 rounded-lg active:scale-95 transition-transform">Launch predictor</button>
312
+ <button id="m-cta-docs-btn" class="text-neon-cyan font-label-caps py-2 hover:underline">Read API docs</button>
313
+ </div>
314
+ </section>
315
+ </main>
316
+ <!-- Footer -->
317
+ <footer class="w-full py-8 mb-16 bg-surface-container-low border-t border-outline-variant flex flex-col items-center gap-4 px-margin-mobile text-center">
318
+ <span class="font-label-caps text-label-caps text-primary">KRONECTOR INTEL</span>
319
+ <div class="flex gap-6">
320
+ <a class="text-on-tertiary-container hover:text-on-surface font-body-fixed text-[11px] uppercase tracking-tighter" href="#">Terms</a>
321
+ <a class="text-on-tertiary-container hover:text-on-surface font-body-fixed text-[11px] uppercase tracking-tighter" href="#">Privacy</a>
322
+ <a id="m-footer-status" class="text-on-tertiary-container hover:text-on-surface font-body-fixed text-[11px] uppercase tracking-tighter cursor-pointer">API_Status</a>
323
+ <a id="m-footer-docs" class="text-on-tertiary-container hover:text-on-surface font-body-fixed text-[11px] uppercase tracking-tighter cursor-pointer">System_Log</a>
324
+ </div>
325
+ <p class="font-body-fixed text-[10px] text-on-surface-variant opacity-60">© 2024 KRONECTOR INTEL. ALL SYSTEMS NOMINAL.</p>
326
+ </footer>
327
+ <!-- BottomNavBar -->
328
+ <nav class="fixed bottom-0 w-full z-50 backdrop-blur-lg bg-surface/90 border-t border-outline-variant flex justify-around items-center h-16 px-4 pb-safe md:hidden">
329
+ <div id="m-nav-dashboard" class="flex flex-col items-center justify-center text-secondary-fixed-dim bg-secondary-container/20 rounded-xl p-2 active:scale-90 duration-150 cursor-pointer">
330
+ <span class="material-symbols-outlined" data-icon="dashboard">dashboard</span>
331
+ </div>
332
+ <div id="m-nav-analytics" class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary transition-colors active:scale-90 duration-150 cursor-pointer">
333
+ <span class="material-symbols-outlined" data-icon="analytics">analytics</span>
334
+ </div>
335
+ <div id="m-nav-radio" class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary transition-colors active:scale-90 duration-150 cursor-pointer">
336
+ <span class="material-symbols-outlined" data-icon="vibration">vibration</span>
337
+ </div>
338
+ <div id="m-nav-api" class="flex flex-col items-center justify-center text-on-surface-variant p-2 hover:text-primary transition-colors active:scale-90 duration-150 cursor-pointer">
339
+ <span class="material-symbols-outlined" data-icon="account_circle">account_circle</span>
340
+ </div>
341
+ </nav>
342
+ <!-- Toast Notification -->
343
+ <div id="m-toast" class="fixed bottom-20 left-4 right-4 z-[100] px-4 py-3 rounded-lg font-body-fixed text-[12px] shadow-2xl border transition-all duration-300 opacity-0 translate-y-4 pointer-events-none text-center" style="background:#201f1f;border-color:#5f3e39;color:#e5e2e1;"></div>
344
+
345
+ <script>
346
+ // ============================================================================
347
+ // KRONECTOR Mobile — API Integration
348
+ // ============================================================================
349
+
350
+ const API_BASE = 'http://localhost:8000';
351
+
352
+ // --- Utility ---
353
+ function $(id) { return document.getElementById(id); }
354
+
355
+ function showToast(msg, type = 'info') {
356
+ const t = $('m-toast');
357
+ t.textContent = msg;
358
+ t.style.borderColor = type === 'error' ? '#93000a' : type === 'success' ? '#00dbe7' : '#5f3e39';
359
+ t.classList.remove('opacity-0', 'translate-y-4', 'pointer-events-none');
360
+ t.classList.add('opacity-100', 'translate-y-0');
361
+ clearTimeout(t._timer);
362
+ t._timer = setTimeout(() => {
363
+ t.classList.add('opacity-0', 'translate-y-4', 'pointer-events-none');
364
+ t.classList.remove('opacity-100', 'translate-y-0');
365
+ }, 4000);
366
+ }
367
+
368
+ function scrollToInput() {
369
+ const input = $('m-terminal-input');
370
+ input.scrollIntoView({ behavior: 'smooth', block: 'center' });
371
+ setTimeout(() => input.focus(), 400);
372
+ }
373
+
374
+ // --- Prediction API ---
375
+ let _isLoading = false;
376
+
377
+ async function runPrediction(query) {
378
+ if (_isLoading) return;
379
+ if (!query || query.trim().length < 3) {
380
+ showToast('Query too short — try a full question', 'error');
381
+ return;
382
+ }
383
+ _isLoading = true;
384
+ $('m-terminal-send').disabled = true;
385
+
386
+ // Loading state
387
+ $('m-terminal-label').textContent = 'PROCESSING...';
388
+ $('m-cond-driver').textContent = '...';
389
+ $('m-cond-team').textContent = '...';
390
+ $('m-cond-prob').textContent = '...';
391
+ $('m-driver-list').innerHTML = '<div class="p-4 flex items-center justify-center"><p class="font-body-fixed text-[12px] text-neon-cyan animate-pulse">⏳ Querying pipeline...</p></div>';
392
+ $('m-shap-content').innerHTML = '<p class="font-body-fixed text-[11px] text-on-surface-variant/50 animate-pulse">Computing...</p>';
393
+ $('m-radio-content').textContent = 'Synthesizing response...';
394
+
395
+ try {
396
+ const res = await fetch(`${API_BASE}/predict/f1`, {
397
+ method: 'POST',
398
+ headers: { 'Content-Type': 'application/json' },
399
+ body: JSON.stringify({ query: query.trim() })
400
+ });
401
+
402
+ if (!res.ok) {
403
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
404
+ throw new Error(err.detail || `API Error ${res.status}`);
405
+ }
406
+
407
+ const data = await res.json();
408
+ renderMobilePrediction(data);
409
+ showToast('Prediction complete ✓', 'success');
410
+ } catch (err) {
411
+ $('m-terminal-label').textContent = 'ERROR';
412
+ $('m-driver-list').innerHTML = `<div class="p-4"><p class="font-body-fixed text-[12px] text-race-red">❌ ${err.message}</p></div>`;
413
+ $('m-shap-content').innerHTML = '<p class="text-race-red text-[11px]">Error</p>';
414
+ $('m-radio-content').textContent = err.message;
415
+ showToast(err.message, 'error');
416
+ } finally {
417
+ _isLoading = false;
418
+ $('m-terminal-send').disabled = false;
419
+ }
420
+ }
421
+
422
+ function renderMobilePrediction(data) {
423
+ const m = data.metadata;
424
+
425
+ // Header label
426
+ $('m-terminal-label').textContent = `${m.season} R${m.round} // ${m.driver_name.toUpperCase()}`;
427
+
428
+ // Conditions bar
429
+ $('m-cond-driver').textContent = m.driver_name;
430
+ $('m-cond-team').textContent = m.team;
431
+ $('m-cond-prob').textContent = `${data.win_probability}%`;
432
+
433
+ // Driver card in list
434
+ const prob = data.win_probability;
435
+ const barColor = prob > 40 ? 'bg-race-red' : prob > 15 ? 'bg-neon-cyan' : 'bg-white/40';
436
+ const textColor = prob > 40 ? 'text-race-red' : prob > 15 ? 'text-neon-cyan' : 'text-on-surface';
437
+ const borderColor = prob > 40 ? 'border-race-red' : prob > 15 ? 'border-neon-cyan' : 'border-[#555]';
438
+
439
+ $('m-driver-list').innerHTML = `
440
+ <div class="p-3 flex justify-between items-center border-l-2 ${borderColor}">
441
+ <div class="flex flex-col">
442
+ <span class="font-label-caps text-on-surface">${m.driver_name.toUpperCase()}</span>
443
+ <span class="font-label-caps text-[10px] text-on-surface-variant/50">${m.team.toUpperCase()}</span>
444
+ </div>
445
+ <div class="text-right">
446
+ <span class="font-body-fixed font-bold ${textColor} text-lg">${prob}%</span>
447
+ <div class="h-1 w-24 bg-[#2D2D2D] rounded-full mt-1 overflow-hidden">
448
+ <div class="h-full ${barColor}" style="width:${Math.min(prob, 100)}%"></div>
449
+ </div>
450
+ </div>
451
+ </div>
452
+ <div class="p-3 flex justify-between items-center text-on-surface-variant/60 border-l-2 border-[#333]">
453
+ <span class="font-label-caps text-[10px]">GRID POSITION</span>
454
+ <span class="font-body-fixed">P${m.grid_position}</span>
455
+ </div>
456
+ ${data.confidence_rating ? `
457
+ <div class="p-3 flex justify-between items-center text-on-surface-variant/60 border-l-2 border-[#333]">
458
+ <span class="font-label-caps text-[10px]">CONFIDENCE</span>
459
+ <span class="font-body-fixed text-neon-cyan">${data.confidence_rating}</span>
460
+ </div>` : ''}
461
+ `;
462
+
463
+ // SHAP bars
464
+ const shapEl = $('m-shap-content');
465
+ if (data.shap_values && Object.keys(data.shap_values).length > 0) {
466
+ const entries = Object.entries(data.shap_values)
467
+ .map(([k, v]) => [k, parseFloat(v)])
468
+ .sort((a, b) => Math.abs(b[1]) - Math.abs(a[1]))
469
+ .slice(0, 4);
470
+ const maxAbs = Math.max(...entries.map(e => Math.abs(e[1])), 0.001);
471
+
472
+ shapEl.innerHTML = entries.map(([feat, val]) => {
473
+ const pct = Math.min(Math.abs(val) / maxAbs * 100, 100).toFixed(0);
474
+ const isPositive = val >= 0;
475
+ const barBg = isPositive ? 'bg-race-red/20' : 'bg-neon-cyan/20';
476
+ const barFill = isPositive ? 'bg-race-red' : 'bg-neon-cyan';
477
+ const textCol = isPositive ? 'text-race-red' : 'text-neon-cyan';
478
+ const dir = isPositive ? 'right-0' : 'left-0';
479
+ return `
480
+ <div class="flex items-center gap-3">
481
+ <span class="font-label-caps text-[10px] w-20 text-on-surface-variant/80">${feat.replace(/_/g, ' ').toUpperCase().slice(0, 10)}</span>
482
+ <div class="flex-1 h-2 ${barBg} rounded-sm relative">
483
+ <div class="absolute ${dir} top-0 h-full w-[${pct}%] ${barFill}"></div>
484
+ </div>
485
+ <span class="font-label-caps text-[10px] ${textCol}">${val >= 0 ? '+' : ''}${val.toFixed(2)}</span>
486
+ </div>
487
+ `;
488
+ }).join('');
489
+ } else {
490
+ shapEl.innerHTML = '<p class="font-body-fixed text-[11px] text-on-surface-variant/50 italic">No SHAP data</p>';
491
+ }
492
+
493
+ // Radio synthesis
494
+ $('m-radio-content').textContent = data.llm_explanation || 'No synthesis available.';
495
+ }
496
+
497
+ // --- Button Wiring ---
498
+
499
+ // Hero buttons
500
+ $('m-hero-run-btn').addEventListener('click', scrollToInput);
501
+ $('m-hero-proof-btn').addEventListener('click', () => {
502
+ $('m-shap-pane').scrollIntoView({ behavior: 'smooth' });
503
+ });
504
+
505
+ // CTA buttons
506
+ $('m-cta-launch-btn').addEventListener('click', scrollToInput);
507
+ $('m-cta-docs-btn').addEventListener('click', () => window.open(`${API_BASE}/docs`, '_blank'));
508
+
509
+ // Terminal send + Enter
510
+ $('m-terminal-send').addEventListener('click', () => runPrediction($('m-terminal-input').value));
511
+ $('m-terminal-input').addEventListener('keydown', (e) => {
512
+ if (e.key === 'Enter') runPrediction($('m-terminal-input').value);
513
+ });
514
+
515
+ // Terminal input focus glow
516
+ $('m-terminal-input').addEventListener('focus', () => {
517
+ $('m-terminal-input').parentElement.classList.add('border-neon-cyan');
518
+ });
519
+ $('m-terminal-input').addEventListener('blur', () => {
520
+ $('m-terminal-input').parentElement.classList.remove('border-neon-cyan');
521
+ });
522
+
523
+ // Bottom nav bar
524
+ $('m-nav-dashboard').addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
525
+ $('m-nav-analytics').addEventListener('click', () => $('m-shap-pane').scrollIntoView({ behavior: 'smooth' }));
526
+ $('m-nav-radio').addEventListener('click', () => $('m-radio-pane').scrollIntoView({ behavior: 'smooth' }));
527
+ $('m-nav-api').addEventListener('click', () => window.open(`${API_BASE}/docs`, '_blank'));
528
+
529
+ // Footer links
530
+ $('m-footer-status').addEventListener('click', async () => {
531
+ try {
532
+ const res = await fetch(`${API_BASE}/health`);
533
+ const data = await res.json();
534
+ showToast(`${data.status} | Model: ${data.model_loaded ? '✓' : '✗'} | Data: ${data.data_available ? '✓' : '✗'}`, data.status === 'healthy' ? 'success' : 'error');
535
+ } catch {
536
+ showToast('API unreachable', 'error');
537
+ }
538
+ });
539
+ $('m-footer-docs').addEventListener('click', () => window.open(`${API_BASE}/docs`, '_blank'));
540
+ </script>
541
+ </body></html>
frontend/next.config.mjs DELETED
@@ -1,6 +0,0 @@
1
- /** @type {import('next').NextConfig} */
2
- const nextConfig = {
3
- /* config options here */
4
- };
5
-
6
- export default nextConfig;
 
 
 
 
 
 
 
frontend/package-lock.json DELETED
The diff for this file is too large to render. See raw diff
 
frontend/package.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "name": "frontend",
3
- "version": "0.1.0",
4
- "private": true,
5
- "scripts": {
6
- "dev": "next dev",
7
- "build": "next build",
8
- "start": "next start",
9
- "lint": "eslint"
10
- },
11
- "dependencies": {
12
- "@radix-ui/react-popover": "^1.1.16",
13
- "@radix-ui/react-slot": "^1.2.5",
14
- "@types/node": "^25.9.2",
15
- "@types/react": "^19.2.17",
16
- "class-variance-authority": "^0.7.1",
17
- "clsx": "^2.1.1",
18
- "lucide-react": "^1.17.0",
19
- "next": "16.2.7",
20
- "react": "19.2.4",
21
- "react-dom": "19.2.4",
22
- "recharts": "^3.8.1",
23
- "tailwind-merge": "^3.6.0",
24
- "typescript": "^6.0.3"
25
- },
26
- "devDependencies": {
27
- "@tailwindcss/postcss": "^4",
28
- "eslint": "^9",
29
- "eslint-config-next": "16.2.7",
30
- "tailwindcss": "^4"
31
- }
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/postcss.config.mjs DELETED
@@ -1,7 +0,0 @@
1
- const config = {
2
- plugins: {
3
- "@tailwindcss/postcss": {},
4
- },
5
- };
6
-
7
- export default config;
 
 
 
 
 
 
 
 
frontend/public/file.svg DELETED
frontend/public/globe.svg DELETED
frontend/public/metrics/confusion_matrix.svg DELETED
frontend/public/metrics/feature_importance.svg DELETED
frontend/public/metrics/pr_curve.svg DELETED
frontend/public/metrics/roc_curve.svg DELETED
frontend/public/next.svg DELETED
frontend/public/vercel.svg DELETED
frontend/public/window.svg DELETED
frontend/tsconfig.json DELETED
@@ -1,26 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "lib": ["dom", "dom.iterable", "esnext"],
4
- "allowJs": true,
5
- "skipLibCheck": true,
6
- "strict": true,
7
- "noEmit": true,
8
- "esModuleInterop": true,
9
- "module": "esnext",
10
- "moduleResolution": "bundler",
11
- "resolveJsonModule": true,
12
- "isolatedModules": true,
13
- "jsx": "preserve",
14
- "incremental": true,
15
- "plugins": [
16
- {
17
- "name": "next"
18
- }
19
- ],
20
- "paths": {
21
- "@/*": ["./*"]
22
- }
23
- },
24
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25
- "exclude": ["node_modules"]
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ml/drift_detection.py CHANGED
@@ -17,7 +17,7 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(name)s | %(level
17
  def detect_drift(
18
  data_path: str = "data_output/fastf1_races.parquet",
19
  current_season: int = 2026,
20
- report_output_path: str = "ui/drift_report.html"
21
  ) -> bool:
22
  """
23
  Detects drift between the reference dataset (historical) and the current dataset.
 
17
  def detect_drift(
18
  data_path: str = "data_output/fastf1_races.parquet",
19
  current_season: int = 2026,
20
+ report_output_path: str = "data_output/drift_report.html"
21
  ) -> bool:
22
  """
23
  Detects drift between the reference dataset (historical) and the current dataset.
mlruns/models/kronector-f1-lgbm/version-1/meta.yaml CHANGED
@@ -1,3 +1,11 @@
 
 
 
 
 
 
 
 
1
  creation_timestamp: 1780335714328
2
  current_stage: None
3
  deployment_job_state: null
 
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
  creation_timestamp: 1780335714328
10
  current_stage: None
11
  deployment_job_state: null