2796gauravc commited on
Commit
9ffd2c3
Β·
verified Β·
1 Parent(s): af4c01b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +2593 -19
index.html CHANGED
@@ -1,19 +1,2593 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Function Arena // BUILD_YOUR_TOOLS</title>
7
+
8
+ <link href="https://api.fontshare.com/v2/css?f[]=clash-display@700,600,500&f[]=space-mono@400,700&display=swap" rel="stylesheet">
9
+
10
+ <!-- Google Analytics -->
11
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-2Q4M55VKPR"></script>
12
+ <script>
13
+ window.dataLayer = window.dataLayer || [];
14
+ function gtag(){dataLayer.push(arguments);}
15
+ gtag('js', new Date());
16
+ gtag('config', 'G-2Q4M55VKPR', {
17
+ page_path: window.location.pathname,
18
+ anonymize_ip: true
19
+ });
20
+ </script>
21
+
22
+ <style>
23
+ :root {
24
+ --void: #050505;
25
+ --panel: #111111;
26
+ --acid: #ccff00;
27
+ --acid-dim: #4d6000;
28
+ --hyper-purple: #7000ff;
29
+ --alert: #ff3300;
30
+ --success: #00ff88;
31
+ --text-main: #f0f0f0;
32
+ --text-muted: #666;
33
+ --border-thick: 3px;
34
+ --hard-shadow: 6px 6px 0px var(--hyper-purple);
35
+ --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
36
+ }
37
+
38
+ * { margin: 0; padding: 0; box-sizing: border-box; }
39
+
40
+ body {
41
+ background-color: var(--void);
42
+ color: var(--text-main);
43
+ font-family: 'Space Mono', monospace;
44
+ min-height: 100vh;
45
+ overflow-x: hidden;
46
+ display: flex;
47
+ flex-direction: column;
48
+ }
49
+
50
+ .noise-overlay {
51
+ position: fixed;
52
+ top: 0; left: 0; width: 100%; height: 100%;
53
+ pointer-events: none;
54
+ z-index: 9999;
55
+ opacity: 0.05;
56
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
57
+ }
58
+
59
+ .scanlines {
60
+ position: fixed;
61
+ top: 0; left: 0; width: 100%; height: 100%;
62
+ background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1));
63
+ background-size: 100% 4px;
64
+ z-index: 9998;
65
+ pointer-events: none;
66
+ }
67
+
68
+ h1, h2, h3, .display-font {
69
+ font-family: 'Clash Display', sans-serif;
70
+ text-transform: uppercase;
71
+ letter-spacing: -0.02em;
72
+ }
73
+
74
+ #boot-sequence {
75
+ position: fixed;
76
+ inset: 0;
77
+ background: var(--void);
78
+ z-index: 10000;
79
+ display: flex;
80
+ flex-direction: column;
81
+ justify-content: flex-end;
82
+ padding: 40px;
83
+ font-size: 14px;
84
+ }
85
+
86
+ .boot-line {
87
+ opacity: 0;
88
+ transform: translateY(10px);
89
+ color: var(--acid);
90
+ margin-bottom: 5px;
91
+ }
92
+
93
+ .app-container {
94
+ max-width: 1800px;
95
+ margin: 0 auto;
96
+ width: 100%;
97
+ padding: 20px;
98
+ display: grid;
99
+ grid-template-columns: 350px 1fr;
100
+ gap: 20px;
101
+ flex: 1;
102
+ opacity: 0;
103
+ transition: opacity 0.5s ease;
104
+ }
105
+
106
+ @media (max-width: 1200px) {
107
+ .app-container { grid-template-columns: 1fr; }
108
+ }
109
+
110
+ .sidebar {
111
+ display: flex;
112
+ flex-direction: column;
113
+ gap: 20px;
114
+ }
115
+
116
+ .panel {
117
+ background: var(--panel);
118
+ border: var(--border-thick) solid var(--text-main);
119
+ padding: 20px;
120
+ position: relative;
121
+ }
122
+
123
+ .panel-title {
124
+ background: var(--text-main);
125
+ color: var(--void);
126
+ padding: 5px 10px;
127
+ font-weight: 700;
128
+ display: inline-block;
129
+ margin-bottom: 15px;
130
+ font-size: 12px;
131
+ }
132
+
133
+ .stat-row {
134
+ display: flex;
135
+ justify-content: space-between;
136
+ align-items: baseline;
137
+ margin-bottom: 10px;
138
+ border-bottom: 1px solid #333;
139
+ padding-bottom: 5px;
140
+ }
141
+
142
+ .stat-value {
143
+ font-family: 'Clash Display';
144
+ font-size: 24px;
145
+ color: var(--acid);
146
+ }
147
+
148
+ .game-area {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: 20px;
152
+ }
153
+
154
+ .level-header {
155
+ border: var(--border-thick) solid var(--acid);
156
+ background: var(--void);
157
+ padding: 30px;
158
+ position: relative;
159
+ box-shadow: var(--hard-shadow);
160
+ }
161
+
162
+ .level-title {
163
+ font-size: 2.5rem;
164
+ line-height: 0.9;
165
+ margin-bottom: 10px;
166
+ }
167
+
168
+ .objective-badge {
169
+ display: inline-block;
170
+ background: var(--hyper-purple);
171
+ color: var(--acid);
172
+ font-weight: 900;
173
+ padding: 8px 16px;
174
+ font-size: 1rem;
175
+ margin-bottom: 15px;
176
+ }
177
+
178
+ .scenario-grid {
179
+ display: grid;
180
+ gap: 10px;
181
+ margin-bottom: 20px;
182
+ }
183
+
184
+ .scenario-card {
185
+ background: rgba(255,255,255,0.02);
186
+ border: 1px solid #333;
187
+ padding: 15px;
188
+ display: grid;
189
+ grid-template-columns: 40px 1fr 100px;
190
+ align-items: center;
191
+ gap: 15px;
192
+ transition: all 0.2s;
193
+ }
194
+
195
+ .scenario-card:hover {
196
+ border-color: var(--acid);
197
+ background: rgba(204, 255, 0, 0.03);
198
+ }
199
+
200
+ .scenario-icon {
201
+ font-size: 24px;
202
+ text-align: center;
203
+ }
204
+
205
+ .scenario-text {
206
+ font-size: 0.9rem;
207
+ line-height: 1.4;
208
+ }
209
+
210
+ .scenario-expected {
211
+ text-align: center;
212
+ font-weight: bold;
213
+ font-size: 0.8rem;
214
+ }
215
+
216
+ .scenario-expected.should-trigger {
217
+ color: var(--success);
218
+ }
219
+
220
+ .scenario-expected.should-not {
221
+ color: var(--alert);
222
+ }
223
+
224
+ .scenario-result {
225
+ position: absolute;
226
+ right: 10px;
227
+ top: 50%;
228
+ transform: translateY(-50%);
229
+ font-size: 20px;
230
+ }
231
+
232
+ .editor-section {
233
+ display: grid;
234
+ grid-template-columns: 1fr 1fr;
235
+ gap: 20px;
236
+ margin-top: 20px;
237
+ }
238
+
239
+ @media (max-width: 900px) {
240
+ .editor-section { grid-template-columns: 1fr; }
241
+ }
242
+
243
+ .editor-panel {
244
+ border: 2px solid #333;
245
+ background: #000;
246
+ }
247
+
248
+ .editor-header {
249
+ background: #1a1a1a;
250
+ padding: 10px 15px;
251
+ border-bottom: 1px solid #333;
252
+ display: flex;
253
+ justify-content: space-between;
254
+ align-items: center;
255
+ }
256
+
257
+ .editor-title {
258
+ color: var(--acid);
259
+ font-size: 0.9rem;
260
+ font-weight: bold;
261
+ }
262
+
263
+ textarea, .json-output {
264
+ width: 100%;
265
+ background: #000;
266
+ border: none;
267
+ color: var(--text-main);
268
+ padding: 20px;
269
+ font-family: 'Space Mono', monospace;
270
+ font-size: 0.9rem;
271
+ min-height: 300px;
272
+ resize: vertical;
273
+ overflow-y: auto;
274
+ }
275
+
276
+ textarea {
277
+ height: auto;
278
+ }
279
+
280
+ textarea:focus {
281
+ outline: none;
282
+ }
283
+
284
+ .json-output {
285
+ color: var(--success);
286
+ overflow-x: auto;
287
+ white-space: pre-wrap;
288
+ }
289
+
290
+ .action-bar {
291
+ display: flex;
292
+ gap: 10px;
293
+ margin-top: 20px;
294
+ }
295
+
296
+ button {
297
+ background: var(--text-main);
298
+ color: var(--void);
299
+ border: none;
300
+ font-family: 'Clash Display', sans-serif;
301
+ font-weight: 700;
302
+ text-transform: uppercase;
303
+ font-size: 1rem;
304
+ padding: 15px 30px;
305
+ cursor: pointer;
306
+ transition: all 0.2s;
307
+ flex: 1;
308
+ }
309
+
310
+ button:hover:not(:disabled) {
311
+ background: var(--acid);
312
+ transform: translate(-4px, -4px);
313
+ box-shadow: 6px 6px 0px var(--text-main);
314
+ }
315
+
316
+ button:active:not(:disabled) {
317
+ transform: translate(0, 0);
318
+ box-shadow: none;
319
+ }
320
+
321
+ button:disabled {
322
+ background: #333;
323
+ color: #666;
324
+ cursor: not-allowed;
325
+ }
326
+
327
+ button.secondary {
328
+ background: transparent;
329
+ border: 2px solid var(--text-main);
330
+ color: var(--text-main);
331
+ }
332
+
333
+ button.secondary:hover:not(:disabled) {
334
+ background: var(--text-main);
335
+ color: var(--void);
336
+ }
337
+
338
+ .hint-panel {
339
+ background: rgba(112, 0, 255, 0.1);
340
+ border: 1px solid var(--hyper-purple);
341
+ padding: 15px;
342
+ margin-top: 20px;
343
+ display: none;
344
+ }
345
+
346
+ .hint-panel.visible {
347
+ display: block;
348
+ }
349
+
350
+ .hint-title {
351
+ color: var(--hyper-purple);
352
+ font-weight: bold;
353
+ margin-bottom: 10px;
354
+ font-size: 0.9rem;
355
+ }
356
+
357
+ .hint-content {
358
+ font-size: 0.85rem;
359
+ line-height: 1.5;
360
+ color: var(--text-muted);
361
+ }
362
+
363
+ .competitor-functions {
364
+ display: grid;
365
+ gap: 10px;
366
+ margin-top: 15px;
367
+ }
368
+
369
+ .competitor-fn {
370
+ background: rgba(255,0,0,0.05);
371
+ border: 1px solid #4a0000;
372
+ padding: 12px;
373
+ font-size: 0.85rem;
374
+ }
375
+
376
+ .fn-name-display {
377
+ color: var(--alert);
378
+ font-weight: bold;
379
+ margin-bottom: 5px;
380
+ }
381
+
382
+ .result-panel {
383
+ margin-top: 20px;
384
+ padding: 20px;
385
+ background: #080808;
386
+ border: 2px solid #333;
387
+ display: none;
388
+ }
389
+
390
+ .result-panel.visible {
391
+ display: block;
392
+ animation: flash 0.2s;
393
+ }
394
+
395
+ .result-header {
396
+ display: flex;
397
+ justify-content: space-between;
398
+ align-items: center;
399
+ margin-bottom: 20px;
400
+ padding-bottom: 15px;
401
+ border-bottom: 2px solid #333;
402
+ }
403
+
404
+ .result-title {
405
+ font-size: 1.5rem;
406
+ font-family: 'Clash Display';
407
+ }
408
+
409
+ .result-score {
410
+ font-size: 2rem;
411
+ font-family: 'Clash Display';
412
+ }
413
+
414
+ .result-title.success, .result-score.success {
415
+ color: var(--success);
416
+ }
417
+
418
+ .result-title.fail, .result-score.fail {
419
+ color: var(--alert);
420
+ }
421
+
422
+ .result-breakdown {
423
+ display: grid;
424
+ gap: 8px;
425
+ }
426
+
427
+ .result-item {
428
+ display: flex;
429
+ justify-content: space-between;
430
+ padding: 8px;
431
+ background: rgba(255,255,255,0.02);
432
+ border-left: 3px solid #333;
433
+ }
434
+
435
+ .result-item.correct {
436
+ border-left-color: var(--success);
437
+ }
438
+
439
+ .result-item.wrong {
440
+ border-left-color: var(--alert);
441
+ }
442
+
443
+ .execution-logs {
444
+ border: 2px solid #333;
445
+ background: #000;
446
+ margin-top: 20px;
447
+ }
448
+
449
+ .logs-header {
450
+ background: #1a1a1a;
451
+ padding: 12px 15px;
452
+ border-bottom: 1px solid #333;
453
+ display: flex;
454
+ justify-content: space-between;
455
+ align-items: center;
456
+ }
457
+
458
+ .logs-title {
459
+ color: var(--acid);
460
+ font-size: 0.9rem;
461
+ font-weight: bold;
462
+ }
463
+
464
+ .logs-clear {
465
+ background: transparent;
466
+ border: 1px solid #333;
467
+ color: #666;
468
+ padding: 4px 12px;
469
+ font-size: 0.8rem;
470
+ cursor: pointer;
471
+ }
472
+
473
+ .logs-clear:hover {
474
+ border-color: #666;
475
+ color: #999;
476
+ }
477
+
478
+ .logs-content {
479
+ padding: 15px;
480
+ font-family: 'Space Mono', monospace;
481
+ font-size: 0.85rem;
482
+ color: var(--text-main);
483
+ max-height: 400px;
484
+ overflow-y: auto;
485
+ background: #0a0a0a;
486
+ }
487
+
488
+ .log-entry {
489
+ margin-bottom: 10px;
490
+ padding-bottom: 10px;
491
+ border-bottom: 1px solid #222;
492
+ opacity: 0;
493
+ animation: fadeIn 0.3s forwards;
494
+ }
495
+
496
+ .log-entry:last-child {
497
+ border-bottom: none;
498
+ }
499
+
500
+ @keyframes fadeIn {
501
+ from { opacity: 0; transform: translateY(-5px); }
502
+ to { opacity: 1; transform: translateY(0); }
503
+ }
504
+
505
+ .log-time {
506
+ color: #666;
507
+ font-size: 0.75rem;
508
+ }
509
+
510
+ .log-label {
511
+ color: var(--acid);
512
+ font-weight: bold;
513
+ display: inline-block;
514
+ min-width: 120px;
515
+ }
516
+
517
+ .log-prompt {
518
+ color: #90ee90;
519
+ padding: 8px;
520
+ background: rgba(144, 238, 144, 0.1);
521
+ border-left: 3px solid #90ee90;
522
+ margin: 8px 0;
523
+ word-break: break-word;
524
+ }
525
+
526
+ .log-tools {
527
+ color: #87ceeb;
528
+ padding: 8px;
529
+ background: rgba(135, 206, 235, 0.1);
530
+ border-left: 3px solid #87ceeb;
531
+ margin: 8px 0;
532
+ max-height: 200px;
533
+ overflow-y: auto;
534
+ }
535
+
536
+ .log-function {
537
+ padding: 4px 8px;
538
+ margin: 4px 0;
539
+ background: #1a1a1a;
540
+ border-radius: 3px;
541
+ }
542
+
543
+ .log-function-name {
544
+ color: var(--acid);
545
+ font-weight: bold;
546
+ }
547
+
548
+ .log-success {
549
+ color: #00ff88;
550
+ }
551
+
552
+ .log-error {
553
+ color: #ff3300;
554
+ }
555
+
556
+ .log-info {
557
+ color: #999;
558
+ }
559
+
560
+ @keyframes flash {
561
+ 0% { background-color: var(--text-main); }
562
+ 100% { background-color: #080808; }
563
+ }
564
+
565
+ .rules-modal {
566
+ position: fixed;
567
+ inset: 0;
568
+ background: rgba(0,0,0,0.9);
569
+ z-index: 5000;
570
+ display: none;
571
+ align-items: center;
572
+ justify-content: center;
573
+ padding: 20px;
574
+ }
575
+
576
+ .rules-modal.visible {
577
+ display: flex;
578
+ }
579
+
580
+ .rules-content {
581
+ background: var(--panel);
582
+ border: var(--border-thick) solid var(--acid);
583
+ max-width: 800px;
584
+ max-height: 90vh;
585
+ overflow-y: auto;
586
+ padding: 30px;
587
+ }
588
+
589
+ .rules-section {
590
+ margin-bottom: 25px;
591
+ }
592
+
593
+ .rules-section h3 {
594
+ color: var(--acid);
595
+ margin-bottom: 10px;
596
+ font-size: 1.2rem;
597
+ }
598
+
599
+ .rules-section p, .rules-section ul {
600
+ line-height: 1.6;
601
+ color: var(--text-muted);
602
+ margin-bottom: 10px;
603
+ }
604
+
605
+ .rules-section ul {
606
+ margin-left: 20px;
607
+ }
608
+
609
+ .rules-section code {
610
+ background: #000;
611
+ padding: 2px 6px;
612
+ border: 1px solid #333;
613
+ color: var(--acid);
614
+ font-size: 0.85rem;
615
+ }
616
+
617
+ .close-rules {
618
+ width: 100%;
619
+ margin-top: 20px;
620
+ }
621
+
622
+ .loader-overlay {
623
+ position: fixed;
624
+ inset: 0;
625
+ background: rgba(5, 5, 5, 0.95);
626
+ z-index: 10001;
627
+ display: flex;
628
+ flex-direction: column;
629
+ align-items: center;
630
+ justify-content: center;
631
+ gap: 30px;
632
+ }
633
+
634
+ .loader-overlay.hidden {
635
+ display: none;
636
+ }
637
+
638
+ .loader-spinner {
639
+ width: 60px;
640
+ height: 60px;
641
+ border: 4px solid var(--panel);
642
+ border-top: 4px solid var(--acid);
643
+ border-radius: 50%;
644
+ animation: spin 1s linear infinite;
645
+ }
646
+
647
+ @keyframes spin {
648
+ 0% { transform: rotate(0deg); }
649
+ 100% { transform: rotate(360deg); }
650
+ }
651
+
652
+ @keyframes slideInRight {
653
+ from {
654
+ transform: translateX(400px);
655
+ opacity: 0;
656
+ }
657
+ to {
658
+ transform: translateX(0);
659
+ opacity: 1;
660
+ }
661
+ }
662
+
663
+ @keyframes slideOutRight {
664
+ from {
665
+ transform: translateX(0);
666
+ opacity: 1;
667
+ }
668
+ to {
669
+ transform: translateX(400px);
670
+ opacity: 0;
671
+ }
672
+ }
673
+
674
+ @keyframes pulse {
675
+ 0%, 100% { transform: scale(1); }
676
+ 50% { transform: scale(1.05); }
677
+ }
678
+
679
+ .scenario-card.correct {
680
+ border-color: var(--success);
681
+ background: rgba(0, 255, 136, 0.1);
682
+ animation: pulse 0.3s;
683
+ }
684
+
685
+ .scenario-card.wrong {
686
+ border-color: var(--alert);
687
+ background: rgba(255, 51, 0, 0.1);
688
+ animation: pulse 0.3s;
689
+ }
690
+
691
+ .loader-text {
692
+ color: var(--acid);
693
+ font-family: 'Clash Display', sans-serif;
694
+ font-size: 1.5rem;
695
+ text-transform: uppercase;
696
+ letter-spacing: 0.1em;
697
+ text-align: center;
698
+ }
699
+
700
+ .loader-progress {
701
+ width: 300px;
702
+ height: 4px;
703
+ background: var(--panel);
704
+ border: 1px solid var(--text-main);
705
+ position: relative;
706
+ overflow: hidden;
707
+ }
708
+
709
+ .loader-progress-bar {
710
+ height: 100%;
711
+ background: var(--acid);
712
+ width: 0%;
713
+ transition: width 0.3s ease;
714
+ box-shadow: 0 0 10px var(--acid);
715
+ }
716
+
717
+ .test-loader {
718
+ position: fixed;
719
+ bottom: 30px;
720
+ right: 30px;
721
+ background: var(--panel);
722
+ border: var(--border-thick) solid var(--acid);
723
+ padding: 20px 30px;
724
+ z-index: 5000;
725
+ display: none;
726
+ align-items: center;
727
+ gap: 15px;
728
+ box-shadow: var(--hard-shadow);
729
+ }
730
+
731
+ .test-loader.visible {
732
+ display: flex;
733
+ animation: slideInUp 0.3s var(--ease-out-expo);
734
+ }
735
+
736
+ @keyframes slideInUp {
737
+ from {
738
+ transform: translateY(100px);
739
+ opacity: 0;
740
+ }
741
+ to {
742
+ transform: translateY(0);
743
+ opacity: 1;
744
+ }
745
+ }
746
+
747
+ .test-loader-spinner {
748
+ width: 24px;
749
+ height: 24px;
750
+ border: 3px solid var(--panel);
751
+ border-top: 3px solid var(--acid);
752
+ border-radius: 50%;
753
+ animation: spin 0.8s linear infinite;
754
+ }
755
+
756
+ .test-loader-text {
757
+ color: var(--acid);
758
+ font-family: 'Space Mono', monospace;
759
+ font-size: 0.9rem;
760
+ }
761
+
762
+ .test-loader-progress {
763
+ color: var(--text-muted);
764
+ font-size: 0.8rem;
765
+ }
766
+
767
+ .model-info {
768
+ position: fixed;
769
+ bottom: 10px;
770
+ left: 10px;
771
+ background: var(--panel);
772
+ border: 1px solid var(--text-main);
773
+ padding: 8px 12px;
774
+ font-size: 0.75rem;
775
+ color: var(--text-muted);
776
+ z-index: 100;
777
+ opacity: 0.7;
778
+ transition: opacity 0.2s;
779
+ display: flex;
780
+ align-items: center;
781
+ gap: 15px;
782
+ flex-wrap: wrap;
783
+ }
784
+
785
+ .model-info:hover {
786
+ opacity: 1;
787
+ }
788
+
789
+ .model-info strong {
790
+ color: var(--acid);
791
+ }
792
+
793
+ .model-info .divider {
794
+ color: #333;
795
+ margin: 0 5px;
796
+ }
797
+
798
+ .model-info .creator-info {
799
+ display: flex;
800
+ align-items: center;
801
+ gap: 8px;
802
+ }
803
+
804
+ .model-info .creator-info a {
805
+ color: var(--acid);
806
+ text-decoration: none;
807
+ transition: color 0.2s;
808
+ }
809
+
810
+ .model-info .creator-info a:hover {
811
+ color: var(--success);
812
+ }
813
+
814
+ .model-info .creator-name {
815
+ color: var(--text-main);
816
+ font-weight: bold;
817
+ }
818
+ </style>
819
+ </head>
820
+ <body>
821
+
822
+ <div class="noise-overlay"></div>
823
+ <div class="scanlines"></div>
824
+
825
+ <div id="boot-sequence"></div>
826
+
827
+ <div class="loader-overlay" id="modelLoader">
828
+ <div class="loader-spinner"></div>
829
+ <div class="loader-text" id="loaderText">LOADING MODEL</div>
830
+ <div class="loader-progress">
831
+ <div class="loader-progress-bar" id="modelProgressBar"></div>
832
+ </div>
833
+ <div id="loaderSubtext" style="color: var(--text-muted); font-size: 0.9rem; text-align: center; max-width: 400px;">
834
+ Initializing model loader...<br>
835
+ <span id="deviceInfo" style="color: var(--acid); font-size: 0.85rem;"></span>
836
+ </div>
837
+ </div>
838
+
839
+ <div class="test-loader" id="testLoader">
840
+ <div class="test-loader-spinner"></div>
841
+ <div>
842
+ <div class="test-loader-text">TESTING FUNCTION</div>
843
+ <div class="test-loader-progress" id="testProgress">Initializing...</div>
844
+ </div>
845
+ </div>
846
+
847
+ <div class="rules-modal" id="rulesModal">
848
+ <div class="rules-content">
849
+ <h2 style="color: var(--acid); margin-bottom: 20px; font-size: 2rem;">FUNCTION ARENA // RULES</h2>
850
+
851
+ <div class="rules-section">
852
+ <h3>🎯 OBJECTIVE</h3>
853
+ <p>Create precise function definitions that trigger in the RIGHT scenarios and stay silent in the WRONG ones. You're competing against other functions for attention.</p>
854
+ </div>
855
+
856
+ <div class="rules-section">
857
+ <h3>πŸ“ HOW TO PLAY</h3>
858
+ <p>1. Review the test scenarios - some should trigger your function (βœ“ SHOULD), others shouldn't (βœ— SHOULD NOT)</p>
859
+ <p>2. Write your function definition with a clear name and description</p>
860
+ <p>3. Add parameters with proper types and descriptions (optional but helps)</p>
861
+ <p>4. Use enums to constrain values when appropriate</p>
862
+ <p>5. Test your function against all scenarios</p>
863
+ </div>
864
+
865
+ <div class="rules-section">
866
+ <h3>πŸ”§ FUNCTION SCHEMA FORMAT</h3>
867
+ <p>Your function follows the JSON Schema / OpenAPI format:</p>
868
+ <ul>
869
+ <li><code>name</code>: Function identifier (e.g., "get_weather")</li>
870
+ <li><code>description</code>: What your function does - BE SPECIFIC</li>
871
+ <li><code>parameters</code>: Object with type definitions</li>
872
+ <li><code>properties</code>: Define each parameter with type, description</li>
873
+ <li><code>required</code>: Array of required parameter names</li>
874
+ <li><code>enum</code>: Use to limit values (e.g., ["celsius", "fahrenheit"])</li>
875
+ </ul>
876
+ </div>
877
+
878
+ <div class="rules-section">
879
+ <h3>πŸ’‘ PRO TIPS</h3>
880
+ <p><strong>Names matter:</strong> Specific names beat generic ones. "fetch_user_profile" > "get_data"</p>
881
+ <p><strong>Descriptions are key:</strong> AI reads your description to decide. Be clear about WHAT and WHEN.</p>
882
+ <p><strong>Use enums wisely:</strong> They constrain inputs and make intent clearer.</p>
883
+ <p><strong>Think like AI:</strong> What keywords would make YOUR function the obvious choice?</p>
884
+ </div>
885
+
886
+ <div class="rules-section">
887
+ <h3>πŸ“Š SCORING</h3>
888
+ <p>+100 points for each correct trigger (when you SHOULD)</p>
889
+ <p>-50 points for each incorrect trigger (when you SHOULDN'T)</p>
890
+ <p>+50 bonus points for each FALSE scenario where you correctly didn't trigger</p>
891
+ <p>Compete against 0-5 competitor functions trying to steal your scenarios!</p>
892
+ </div>
893
+
894
+ <div class="rules-section">
895
+ <h3>πŸŽ“ LEARNING PATH</h3>
896
+ <p>Levels progress from basic scenarios to complex edge cases:</p>
897
+ <p>β€’ <strong>Novice:</strong> Simple, clear scenarios</p>
898
+ <p>β€’ <strong>Intermediate:</strong> Ambiguous queries, need precision</p>
899
+ <p>β€’ <strong>Advanced:</strong> Multiple competitors, overlapping domains</p>
900
+ <p>β€’ <strong>Expert:</strong> Complex parameters, enums required</p>
901
+ <p>β€’ <strong>Master:</strong> Multi-domain confusion, extreme precision needed</p>
902
+ </div>
903
+
904
+ <button class="close-rules" onclick="toggleRules()">CLOSE // BEGIN TRAINING</button>
905
+ </div>
906
+ </div>
907
+
908
+ <div class="app-container" id="app">
909
+
910
+ <aside class="sidebar">
911
+ <div class="panel">
912
+ <div class="panel-title">OPERATOR_STATS</div>
913
+ <div class="stat-row">
914
+ <span>LEVEL</span>
915
+ <span class="stat-value" id="levelDisp">01</span>
916
+ </div>
917
+ <div class="stat-row">
918
+ <span>SCENARIOS</span>
919
+ <span class="stat-value" id="scenarioCount">0</span>
920
+ </div>
921
+ <div class="stat-row">
922
+ <span>SCORE</span>
923
+ <span class="stat-value" id="scoreDisp">0000</span>
924
+ </div>
925
+ <div class="stat-row">
926
+ <span>ACCURACY</span>
927
+ <span class="stat-value" id="accuracyDisp" style="color: var(--success)">--</span>
928
+ </div>
929
+ <div class="stat-row" id="streakRow" style="display: none;">
930
+ <span>STREAK</span>
931
+ <span class="stat-value" id="streakDisp" style="color: var(--hyper-purple)">0</span>
932
+ </div>
933
+ </div>
934
+
935
+
936
+ <div class="panel">
937
+ <div class="panel-title">COMPETITOR_FUNCTIONS</div>
938
+ <div id="competitorList" style="font-size: 0.8rem; color: #666;">
939
+ None loaded...
940
+ </div>
941
+ </div>
942
+
943
+ <div class="panel">
944
+ <div class="panel-title">ACHIEVEMENTS</div>
945
+ <div id="achievementsList" style="font-size: 0.8rem; color: #666; min-height: 40px;">
946
+ No achievements yet...
947
+ </div>
948
+ </div>
949
+
950
+ <button onclick="toggleRules()" style="border: 1px solid var(--text-main); background: transparent;">
951
+ VIEW RULES [?]
952
+ </button>
953
+
954
+ <button onclick="toggleHints()" style="border: 1px solid var(--hyper-purple); background: transparent; color: var(--hyper-purple);">
955
+ SHOW HINTS [πŸ’‘]
956
+ </button>
957
+
958
+ <button onclick="resetProgress()" style="border: 1px solid var(--alert); background: transparent; color: var(--alert); margin-top: 10px; font-size: 0.85rem;">
959
+ RESET PROGRESS [⚠️]
960
+ </button>
961
+ </aside>
962
+
963
+ <main class="game-area">
964
+ <header class="level-header">
965
+ <div class="objective-badge" id="objectiveBadge">BUILD YOUR FUNCTION</div>
966
+ <h1 class="level-title" id="lvlName">INITIALIZING...</h1>
967
+ <p style="max-width: 800px; color: var(--text-muted); margin-top: 10px;" id="lvlDesc">Loading challenge parameters...</p>
968
+ </header>
969
+
970
+ <div class="panel" style="border-color: var(--acid);">
971
+ <div class="panel-title" style="background: var(--acid); color: black;">TEST_SCENARIOS</div>
972
+ <div class="scenario-grid" id="scenarioGrid"></div>
973
+ </div>
974
+
975
+ <div class="editor-section">
976
+ <div class="editor-panel">
977
+ <div class="editor-header">
978
+ <span class="editor-title">YOUR FUNCTION DEFINITION</span>
979
+ <span style="color: #666; font-size: 0.8rem;">βœ“ Pre-filled template β€’ Edit to refine</span>
980
+ </div>
981
+ <textarea id="functionEditor" placeholder='{\n "name": "my_function",\n "description": "What my function does...",\n "parameters": {\n "type": "object",\n "properties": {\n "param1": {\n "type": "string",\n "description": "First parameter"\n }\n },\n "required": ["param1"]\n }\n}'></textarea>
982
+ </div>
983
+
984
+ <div class="editor-panel">
985
+ <div class="editor-header">
986
+ <span class="editor-title">PARSED OUTPUT</span>
987
+ <span style="color: #666; font-size: 0.8rem;">Validation</span>
988
+ </div>
989
+ <div class="json-output" id="jsonOutput">// Your JSON will be validated here...</div>
990
+ </div>
991
+ </div>
992
+
993
+ <div class="hint-panel" id="hintPanel">
994
+ <div class="hint-title">πŸ’‘ STRATEGIC HINTS</div>
995
+ <div class="hint-content" id="hintContent"></div>
996
+ </div>
997
+
998
+ <div class="action-bar">
999
+ <button id="testBtn" onclick="testFunction()">TEST FUNCTION</button>
1000
+ <button class="secondary" onclick="resetEditor()">RESET</button>
1001
+ <button class="secondary" onclick="loadTemplate()">πŸ“‹ STANDARD TEMPLATE</button>
1002
+ <button class="secondary" id="nextBtn" onclick="nextLevel()" style="display: none;">NEXT LEVEL β†’</button>
1003
+ </div>
1004
+
1005
+ <div class="result-panel" id="resultPanel">
1006
+ <div class="result-header">
1007
+ <span class="result-title" id="resultTitle">RESULTS</span>
1008
+ <span class="result-score" id="resultScore">+0</span>
1009
+ </div>
1010
+ <div class="result-breakdown" id="resultBreakdown"></div>
1011
+ </div>
1012
+
1013
+ <div class="execution-logs">
1014
+ <div class="logs-header">
1015
+ <span class="logs-title">βš™οΈ EXECUTION LOGS // Real-time Model Behavior</span>
1016
+ <button class="logs-clear" onclick="clearLogs()">CLEAR LOGS</button>
1017
+ </div>
1018
+ <div class="logs-content" id="logsContent">
1019
+ <div style="color: #666; text-align: center; padding: 40px 20px;">
1020
+ Logs will appear here when you test a function...
1021
+ </div>
1022
+ </div>
1023
+ </div>
1024
+
1025
+ </main>
1026
+ </div>
1027
+
1028
+ <div class="model-info" id="modelInfo" style="display: none;">
1029
+ <div>
1030
+ <strong>Model:</strong> <span id="modelName">-</span> |
1031
+ <strong>Version:</strong> <span id="modelVersion">-</span> |
1032
+ <strong>Quantization:</strong> <span id="modelQuantization">-</span> |
1033
+ <strong>Device:</strong> <span id="modelDevice">-</span> |
1034
+
1035
+ <strong>Made by Gaurav Chauhan</strong> |
1036
+ <strong>2796gaurav@gmail.com</strong> |
1037
+ <strong><a href="https://www.linkedin.com/in/gauravc2708/" target="_blank" rel="noopener noreferrer">LinkedIn</a></strong> |
1038
+
1039
+ </div>
1040
+
1041
+ </div>
1042
+
1043
+ <script type="module">
1044
+ // Import idb for IndexedDB
1045
+ import { openDB } from 'https://cdn.jsdelivr.net/npm/idb@7/+esm';
1046
+
1047
+ // 1. Add IndexedDB for explicit cache control
1048
+ const dbPromise = openDB('function-arena-cache', 1, {
1049
+ upgrade(db) {
1050
+ db.createObjectStore('models');
1051
+ db.createObjectStore('tokenizers');
1052
+ db.createObjectStore('level-data');
1053
+ },
1054
+ });
1055
+
1056
+ // 3. Add telemetry for optimization insights
1057
+ const telemetry = {
1058
+ modelLoadTime: 0,
1059
+ averageGenerationTime: 0,
1060
+ cacheHitRate: 0,
1061
+ deviceType: null,
1062
+ generationTimes: [],
1063
+ cacheHits: 0,
1064
+ cacheMisses: 0,
1065
+ init() {
1066
+ // Detect device type
1067
+ const hasWebGPU = !!navigator.gpu;
1068
+ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1069
+ this.deviceType = hasWebGPU ? 'webgpu' : (isMobile ? 'mobile-cpu' : 'desktop-cpu');
1070
+
1071
+ // Track page load
1072
+ if (typeof gtag !== 'undefined') {
1073
+ gtag('event', 'page_load', {
1074
+ device_type: this.deviceType,
1075
+ timestamp: new Date().toISOString()
1076
+ });
1077
+ }
1078
+ },
1079
+ trackModelLoad(time) {
1080
+ this.modelLoadTime = time;
1081
+ if (typeof gtag !== 'undefined') {
1082
+ gtag('event', 'model_load', {
1083
+ load_time: time,
1084
+ device_type: this.deviceType
1085
+ });
1086
+ }
1087
+ },
1088
+ trackGeneration(time) {
1089
+ this.generationTimes.push(time);
1090
+ // Keep only last 100 generations
1091
+ if (this.generationTimes.length > 100) {
1092
+ this.generationTimes.shift();
1093
+ }
1094
+ this.averageGenerationTime = this.generationTimes.reduce((a, b) => a + b, 0) / this.generationTimes.length;
1095
+
1096
+ if (typeof gtag !== 'undefined') {
1097
+ gtag('event', 'function_generation', {
1098
+ generation_time: time,
1099
+ average_time: this.averageGenerationTime
1100
+ });
1101
+ }
1102
+ },
1103
+ trackCacheHit(hit) {
1104
+ if (hit) {
1105
+ this.cacheHits++;
1106
+ } else {
1107
+ this.cacheMisses++;
1108
+ }
1109
+ const total = this.cacheHits + this.cacheMisses;
1110
+ this.cacheHitRate = total > 0 ? (this.cacheHits / total) * 100 : 0;
1111
+
1112
+ if (typeof gtag !== 'undefined') {
1113
+ gtag('event', 'cache_access', {
1114
+ cache_hit: hit,
1115
+ hit_rate: this.cacheHitRate
1116
+ });
1117
+ }
1118
+ },
1119
+ trackLevelComplete(levelId, score, passed) {
1120
+ if (typeof gtag !== 'undefined') {
1121
+ gtag('event', 'level_complete', {
1122
+ level_id: levelId,
1123
+ score: score,
1124
+ passed: passed,
1125
+ average_generation_time: this.averageGenerationTime,
1126
+ cache_hit_rate: this.cacheHitRate
1127
+ });
1128
+ }
1129
+ },
1130
+ getMetrics() {
1131
+ return {
1132
+ modelLoadTime: this.modelLoadTime,
1133
+ averageGenerationTime: this.averageGenerationTime,
1134
+ cacheHitRate: this.cacheHitRate,
1135
+ deviceType: this.deviceType,
1136
+ totalGenerations: this.generationTimes.length
1137
+ };
1138
+ }
1139
+ };
1140
+
1141
+ // Initialize telemetry
1142
+ telemetry.init();
1143
+
1144
+ // 2. Add cache warmup on idle
1145
+ function setupCacheWarmup() {
1146
+ if ('requestIdleCallback' in window) {
1147
+ requestIdleCallback(() => {
1148
+ // Preload next level's competitor functions
1149
+ const nextLevel = levels[state.levelIdx + 1];
1150
+ if (nextLevel) {
1151
+ // Prepare next level data in IndexedDB
1152
+ dbPromise.then(db => {
1153
+ return db.put('level-data', nextLevel, `level-${nextLevel.id}`);
1154
+ }).then(() => {
1155
+ console.log('βœ“ Next level data cached');
1156
+ telemetry.trackCacheHit(true);
1157
+ }).catch(err => {
1158
+ console.log('Cache warmup failed:', err);
1159
+ });
1160
+ }
1161
+ }, { timeout: 5000 });
1162
+ } else {
1163
+ // Fallback for browsers without requestIdleCallback
1164
+ setTimeout(() => {
1165
+ const nextLevel = levels[state.levelIdx + 1];
1166
+ if (nextLevel) {
1167
+ dbPromise.then(db => {
1168
+ return db.put('level-data', nextLevel, `level-${nextLevel.id}`);
1169
+ }).catch(err => console.log('Cache warmup failed:', err));
1170
+ }
1171
+ }, 2000);
1172
+ }
1173
+ }
1174
+
1175
+ // GAME LEVELS
1176
+ const levels = [
1177
+ {
1178
+ id: 1,
1179
+ title: "BASIC_TRIGGER",
1180
+ description: "Simple weather function. Make it trigger for weather queries only.",
1181
+ difficulty: "NOVICE",
1182
+ scenarios: [
1183
+ { query: "What's the weather in Tokyo?", shouldTrigger: true, icon: "β˜€οΈ" },
1184
+ { query: "Get weather for London", shouldTrigger: true, icon: "🌧️" },
1185
+ { query: "Tell me about Paris history", shouldTrigger: false, icon: "πŸ“š" },
1186
+ { query: "What time is it?", shouldTrigger: false, icon: "πŸ•" },
1187
+ ],
1188
+ competitors: [],
1189
+ hints: [
1190
+ "Focus on weather-related keywords in your description",
1191
+ "Use a clear name like 'get_weather' or 'fetch_weather'",
1192
+ "Add a location parameter to make it more specific"
1193
+ ],
1194
+ starterCode: {
1195
+ name: "get_weather",
1196
+ description: "Get current weather information for a specified location",
1197
+ parameters: {
1198
+ type: "object",
1199
+ properties: {
1200
+ location: {
1201
+ type: "string",
1202
+ description: "The city or location name"
1203
+ }
1204
+ },
1205
+ required: ["location"]
1206
+ }
1207
+ }
1208
+ },
1209
+ {
1210
+ id: 2,
1211
+ title: "PRECISION_TARGETING",
1212
+ description: "Build a user profile function. Avoid triggering on similar but different queries.",
1213
+ difficulty: "INTERMEDIATE",
1214
+ scenarios: [
1215
+ { query: "Get user profile for john_doe", shouldTrigger: true, icon: "πŸ‘€" },
1216
+ { query: "Fetch user info for ID 12345", shouldTrigger: true, icon: "πŸ”" },
1217
+ { query: "Show user analytics", shouldTrigger: false, icon: "πŸ“Š" },
1218
+ { query: "Delete user account", shouldTrigger: false, icon: "πŸ—‘οΈ" },
1219
+ { query: "Update user settings", shouldTrigger: false, icon: "βš™οΈ" },
1220
+ { query: "Get profile data", shouldTrigger: true, icon: "πŸ’Ύ" },
1221
+ ],
1222
+ competitors: [
1223
+ {
1224
+ name: "get_user_analytics",
1225
+ description: "Retrieve user behavior analytics and statistics"
1226
+ },
1227
+ {
1228
+ name: "update_user",
1229
+ description: "Update user information and settings"
1230
+ }
1231
+ ],
1232
+ hints: [
1233
+ "Emphasize 'retrieve' or 'get' operations, not modify/delete",
1234
+ "Specify that it's for profile/information retrieval",
1235
+ "Distinguish from analytics (which is aggregate data)",
1236
+ "Use parameters to show what data you return"
1237
+ ],
1238
+ starterCode: {
1239
+ name: "get_user_profile",
1240
+ description: "Retrieve user profile information including name, email, and account details",
1241
+ parameters: {
1242
+ type: "object",
1243
+ properties: {
1244
+ user_id: {
1245
+ type: "string",
1246
+ description: "The unique identifier or username of the user"
1247
+ }
1248
+ },
1249
+ required: ["user_id"]
1250
+ }
1251
+ }
1252
+ },
1253
+ {
1254
+ id: 3,
1255
+ title: "ENUM_MASTERY",
1256
+ description: "Create a task management function. Use enums to constrain priority and status.",
1257
+ difficulty: "ADVANCED",
1258
+ scenarios: [
1259
+ { query: "Create high priority task", shouldTrigger: true, icon: "πŸ“" },
1260
+ { query: "Add new task", shouldTrigger: true, icon: "βž•" },
1261
+ { query: "List completed tasks", shouldTrigger: false, icon: "πŸ“‹" },
1262
+ { query: "Mark task as done", shouldTrigger: false, icon: "βœ…" },
1263
+ { query: "Create task with low priority", shouldTrigger: true, icon: "πŸ›" },
1264
+ { query: "Delete tasks", shouldTrigger: false, icon: "πŸ—‘οΈ" },
1265
+ { query: "Update task deadline", shouldTrigger: false, icon: "πŸ“…" },
1266
+ ],
1267
+ competitors: [
1268
+ {
1269
+ name: "list_tasks",
1270
+ description: "Retrieve and list existing tasks with filters"
1271
+ },
1272
+ {
1273
+ name: "update_task_status",
1274
+ description: "Change the status of an existing task"
1275
+ },
1276
+ {
1277
+ name: "delete_task",
1278
+ description: "Remove a task from the system"
1279
+ }
1280
+ ],
1281
+ hints: [
1282
+ "Focus on CREATE/ADD operations only",
1283
+ "Use enum for priority: ['low', 'medium', 'high']",
1284
+ "Use enum for initial status: ['pending', 'in_progress']",
1285
+ "Make your description explicitly mention 'create' or 'add'",
1286
+ "Distinguish from update, delete, and list operations"
1287
+ ],
1288
+ starterCode: {
1289
+ name: "create_task",
1290
+ description: "Create a new task with title, description, priority level, and initial status",
1291
+ parameters: {
1292
+ type: "object",
1293
+ properties: {
1294
+ title: {
1295
+ type: "string",
1296
+ description: "The task title or name"
1297
+ },
1298
+ priority: {
1299
+ type: "string",
1300
+ enum: ["low", "medium", "high"],
1301
+ description: "Priority level of the task"
1302
+ },
1303
+ status: {
1304
+ type: "string",
1305
+ enum: ["pending", "in_progress"],
1306
+ description: "Initial status of the task"
1307
+ }
1308
+ },
1309
+ required: ["title", "priority"]
1310
+ }
1311
+ }
1312
+ },
1313
+ {
1314
+ id: 4,
1315
+ title: "MULTI_DOMAIN_CHAOS",
1316
+ description: "Calendar event creation vs. reminders vs. notifications. Extreme precision required.",
1317
+ difficulty: "EXPERT",
1318
+ scenarios: [
1319
+ { query: "Schedule meeting for tomorrow", shouldTrigger: true, icon: "πŸ“…" },
1320
+ { query: "Create calendar event", shouldTrigger: true, icon: "πŸ‘₯" },
1321
+ { query: "Set reminder to call mom", shouldTrigger: false, icon: "πŸ””" },
1322
+ { query: "Send notification", shouldTrigger: false, icon: "πŸ“¬" },
1323
+ { query: "Add event to calendar", shouldTrigger: true, icon: "πŸŽ‚" },
1324
+ { query: "Remind me about meeting", shouldTrigger: false, icon: "⏰" },
1325
+ { query: "List calendar events", shouldTrigger: false, icon: "πŸ“‹" },
1326
+ { query: "Block time on calendar", shouldTrigger: true, icon: "🎯" },
1327
+ ],
1328
+ competitors: [
1329
+ {
1330
+ name: "create_reminder",
1331
+ description: "Set a reminder notification for a specific time or event"
1332
+ },
1333
+ {
1334
+ name: "send_notification",
1335
+ description: "Send an immediate or scheduled notification to the user"
1336
+ },
1337
+ {
1338
+ name: "list_events",
1339
+ description: "Retrieve calendar events within a date range"
1340
+ },
1341
+ {
1342
+ name: "update_event",
1343
+ description: "Modify an existing calendar event"
1344
+ }
1345
+ ],
1346
+ hints: [
1347
+ "Calendar events are time-blocked entries with start/end times",
1348
+ "Reminders are notifications, not calendar entries",
1349
+ "Use parameters: title, start_time, end_time, location",
1350
+ "Emphasize 'calendar', 'schedule', 'meeting', 'event' in description",
1351
+ "Explicitly exclude reminders and notifications in your description"
1352
+ ],
1353
+ starterCode: {
1354
+ name: "create_calendar_event",
1355
+ description: "Create a calendar event with time-blocked scheduling for meetings and appointments",
1356
+ parameters: {
1357
+ type: "object",
1358
+ properties: {
1359
+ title: {
1360
+ type: "string",
1361
+ description: "Event title or meeting name"
1362
+ },
1363
+ start_time: {
1364
+ type: "string",
1365
+ description: "Event start time (ISO 8601 format)"
1366
+ },
1367
+ end_time: {
1368
+ type: "string",
1369
+ description: "Event end time (ISO 8601 format)"
1370
+ },
1371
+ location: {
1372
+ type: "string",
1373
+ description: "Physical or virtual meeting location"
1374
+ }
1375
+ },
1376
+ required: ["title", "start_time", "end_time"]
1377
+ }
1378
+ }
1379
+ },
1380
+ {
1381
+ id: 5,
1382
+ title: "ULTIMATE_PRECISION",
1383
+ description: "Payment processing with strict parameters. Security-critical function calling.",
1384
+ difficulty: "MASTER",
1385
+ scenarios: [
1386
+ { query: "Process payment $99.99", shouldTrigger: true, icon: "πŸ’³" },
1387
+ { query: "Charge customer $250", shouldTrigger: true, icon: "πŸ’°" },
1388
+ { query: "Show payment history", shouldTrigger: false, icon: "πŸ“Š" },
1389
+ { query: "Refund transaction", shouldTrigger: false, icon: "↩️" },
1390
+ { query: "Process payment $1500", shouldTrigger: true, icon: "🏦" },
1391
+ { query: "Verify payment status", shouldTrigger: false, icon: "πŸ”" },
1392
+ { query: "Cancel payment", shouldTrigger: false, icon: "❌" },
1393
+ { query: "Charge $45.50", shouldTrigger: true, icon: "πŸ’΅" },
1394
+ { query: "Update payment method", shouldTrigger: false, icon: "✏️" },
1395
+ ],
1396
+ competitors: [
1397
+ {
1398
+ name: "refund_payment",
1399
+ description: "Issue a refund for a completed transaction"
1400
+ },
1401
+ {
1402
+ name: "verify_payment",
1403
+ description: "Check the status and validity of a payment transaction"
1404
+ },
1405
+ {
1406
+ name: "get_payment_history",
1407
+ description: "Retrieve past payment transactions and history"
1408
+ },
1409
+ {
1410
+ name: "cancel_payment",
1411
+ description: "Cancel a pending or scheduled payment"
1412
+ },
1413
+ {
1414
+ name: "update_payment_method",
1415
+ description: "Modify or change stored payment method information"
1416
+ }
1417
+ ],
1418
+ hints: [
1419
+ "This is about PROCESSING/CHARGING, not viewing or refunding",
1420
+ "Require amount parameter with number type",
1421
+ "Use enum for payment_method: ['credit_card', 'debit_card', 'bank_transfer', 'saved_method']",
1422
+ "Add currency parameter with enum: ['USD', 'EUR', 'GBP']",
1423
+ "Make description security-conscious and specific to charging",
1424
+ "Distinguish clearly from refund, verify, and cancel operations"
1425
+ ],
1426
+ starterCode: {
1427
+ name: "process_payment",
1428
+ description: "Process a payment transaction with secure handling of payment method and currency",
1429
+ parameters: {
1430
+ type: "object",
1431
+ properties: {
1432
+ amount: {
1433
+ type: "number",
1434
+ description: "Payment amount in the specified currency"
1435
+ },
1436
+ currency: {
1437
+ type: "string",
1438
+ enum: ["USD", "EUR", "GBP"],
1439
+ description: "Currency code for the transaction"
1440
+ },
1441
+ payment_method: {
1442
+ type: "string",
1443
+ enum: ["credit_card", "debit_card", "bank_transfer", "saved_method"],
1444
+ description: "The payment method to use"
1445
+ },
1446
+ customer_id: {
1447
+ type: "string",
1448
+ description: "Customer identifier for the transaction"
1449
+ }
1450
+ },
1451
+ required: ["amount", "currency", "payment_method", "customer_id"]
1452
+ }
1453
+ }
1454
+ }
1455
+ ];
1456
+
1457
+ let state = {
1458
+ levelIdx: 0,
1459
+ totalScore: 0,
1460
+ model: null,
1461
+ tokenizer: null,
1462
+ booted: false,
1463
+ currentFunction: null,
1464
+ achievements: [],
1465
+ streak: 0,
1466
+ bestStreak: 0,
1467
+ levelAttempts: {},
1468
+ hintsUsed: 0,
1469
+ modelInfo: {
1470
+ name: null,
1471
+ version: null,
1472
+ quantization: null,
1473
+ device: null
1474
+ }
1475
+ };
1476
+
1477
+ // Load state from localStorage
1478
+ function loadState() {
1479
+ try {
1480
+ const saved = localStorage.getItem('functionArenaState');
1481
+ if (saved) {
1482
+ const parsed = JSON.parse(saved);
1483
+ state.totalScore = parsed.totalScore || 0;
1484
+ state.achievements = parsed.achievements || [];
1485
+ state.bestStreak = parsed.bestStreak || 0;
1486
+ state.levelAttempts = parsed.levelAttempts || {};
1487
+ state.hintsUsed = parsed.hintsUsed || 0;
1488
+ }
1489
+ } catch(e) {
1490
+ console.log('No saved state found');
1491
+ }
1492
+ }
1493
+
1494
+ // Save state to localStorage
1495
+ function saveState() {
1496
+ try {
1497
+ localStorage.setItem('functionArenaState', JSON.stringify({
1498
+ totalScore: state.totalScore,
1499
+ achievements: state.achievements,
1500
+ bestStreak: state.bestStreak,
1501
+ levelAttempts: state.levelAttempts,
1502
+ hintsUsed: state.hintsUsed
1503
+ }));
1504
+ } catch(e) {
1505
+ console.error('Failed to save state:', e);
1506
+ }
1507
+ }
1508
+
1509
+ // Initialize state on load
1510
+ loadState();
1511
+
1512
+ // Boot Sequence
1513
+ async function bootSequence() {
1514
+ const bootLines = [
1515
+ "INITIALIZING FUNCTION ARENA v2.0...",
1516
+ "OPTIMIZING MODEL LOAD [Q4 QUANTIZATION + GPU]...",
1517
+ "LOADING NEURAL WEIGHTS [onnx-community/functiongemma-270m]...",
1518
+ "MOUNTING EDUCATION MODULE...",
1519
+ "ESTABLISHING SEMANTIC PARSER...",
1520
+ "INDEXING FUNCTION SCHEMA LIBRARY...",
1521
+ "TRAINING ENVIRONMENT READY.",
1522
+ "SYSTEM ONLINE."
1523
+ ];
1524
+
1525
+ const container = document.getElementById('boot-sequence');
1526
+
1527
+ for(let i=0; i<bootLines.length; i++) {
1528
+ const line = document.createElement('div');
1529
+ line.className = 'boot-line';
1530
+ line.textContent = `> ${bootLines[i]}`;
1531
+ container.appendChild(line);
1532
+
1533
+ line.offsetHeight;
1534
+ line.style.opacity = 1;
1535
+ line.style.transform = "translateY(0)";
1536
+
1537
+ await new Promise(r => setTimeout(r, 400));
1538
+ }
1539
+
1540
+ await loadModel();
1541
+
1542
+ container.style.transition = "opacity 0.5s";
1543
+ container.style.opacity = 0;
1544
+ setTimeout(() => {
1545
+ container.style.display = 'none';
1546
+ document.getElementById('app').style.opacity = 1;
1547
+ state.booted = true;
1548
+ renderLevel();
1549
+ toggleRules(); // Show rules on first load
1550
+ }, 500);
1551
+ }
1552
+
1553
+ async function loadModel() {
1554
+ const loadStartTime = performance.now();
1555
+ const loader = document.getElementById('modelLoader');
1556
+ const progressBar = document.getElementById('modelProgressBar');
1557
+ const loaderText = document.getElementById('loaderText');
1558
+ const loaderSubtext = document.getElementById('loaderSubtext');
1559
+ const deviceInfo = document.getElementById('deviceInfo');
1560
+
1561
+ loader.classList.remove('hidden');
1562
+
1563
+ // Detect device capabilities
1564
+ const hasWebGPU = !!navigator.gpu;
1565
+ const modelId = "onnx-community/functiongemma-270m-it-ONNX";
1566
+
1567
+ // Update device info
1568
+ if (hasWebGPU) {
1569
+ deviceInfo.textContent = 'βœ“ WebGPU detected - GPU acceleration enabled';
1570
+ deviceInfo.style.color = 'var(--success)';
1571
+ } else {
1572
+ deviceInfo.textContent = '⚠ WebGPU unavailable - using CPU (WASM)';
1573
+ deviceInfo.style.color = 'var(--text-muted)';
1574
+ }
1575
+
1576
+ try {
1577
+ // Step 1: Import library
1578
+ loaderText.textContent = 'IMPORTING TRANSFORMERS.JS';
1579
+ loaderSubtext.innerHTML = 'Loading library from CDN...<br><span style="color: var(--text-muted); font-size: 0.85rem;">~2MB download</span>';
1580
+ progressBar.style.width = '15%';
1581
+
1582
+ const { env, AutoTokenizer, AutoModelForCausalLM } = await import(
1583
+ "https://cdn.jsdelivr.net/npm/@huggingface/transformers@latest"
1584
+ );
1585
+
1586
+ env.allowRemoteModels = true;
1587
+ env.allowLocalModels = false;
1588
+ env.useBrowserCache = true; // Enable browser cache explicitly
1589
+
1590
+ // Step 2: Load tokenizer
1591
+ loaderText.textContent = 'LOADING TOKENIZER';
1592
+ loaderSubtext.innerHTML = 'Loading tokenizer configuration...<br><span style="color: var(--text-muted); font-size: 0.85rem;">Checking cache...</span>';
1593
+ progressBar.style.width = '30%';
1594
+
1595
+ let tokenizerCached = false;
1596
+ state.tokenizer = await AutoTokenizer.from_pretrained(modelId, {
1597
+ progress_callback: (progress) => {
1598
+ if (progress && (progress.status === 'done' || progress.file)) {
1599
+ tokenizerCached = progress.cached || false;
1600
+ if (tokenizerCached) {
1601
+ loaderSubtext.innerHTML = 'βœ“ Tokenizer loaded from cache<br><span style="color: var(--success); font-size: 0.85rem;">Instant load!</span>';
1602
+ } else {
1603
+ loaderSubtext.innerHTML = 'βœ“ Tokenizer downloaded<br><span style="color: var(--text-muted); font-size: 0.85rem;">Cached for next time</span>';
1604
+ }
1605
+ console.log(`βœ“ Tokenizer ${tokenizerCached ? 'cached' : 'downloaded'}`);
1606
+ }
1607
+ }
1608
+ });
1609
+
1610
+ // Step 3: Load model with optimization
1611
+ progressBar.style.width = '40%';
1612
+
1613
+ // Determine best configuration
1614
+ let modelConfig = {};
1615
+ let modelCached = false;
1616
+
1617
+ if (hasWebGPU) {
1618
+ // Use WebGPU with q4 quantization for best performance
1619
+ modelConfig = {
1620
+ dtype: "q4", // 4-bit quantization: ~68MB instead of ~270MB
1621
+ device: "webgpu"
1622
+ };
1623
+ loaderText.textContent = 'LOADING MODEL (GPU + Q4)';
1624
+ loaderSubtext.innerHTML = 'Loading quantized model with GPU acceleration...<br><span style="color: var(--success); font-size: 0.85rem;">~68MB download (4x smaller!)</span>';
1625
+ console.log("βœ“ Using WebGPU with q4 quantization");
1626
+ } else {
1627
+ // Fallback to WASM with q8 for better CPU performance
1628
+ modelConfig = {
1629
+ dtype: "q8", // 8-bit quantization: ~135MB, better CPU performance
1630
+ device: "wasm"
1631
+ };
1632
+ loaderText.textContent = 'LOADING MODEL (CPU + Q8)';
1633
+ loaderSubtext.innerHTML = 'Loading quantized model for CPU...<br><span style="color: var(--text-muted); font-size: 0.85rem;">~135MB download</span>';
1634
+ console.log("⚠ WebGPU unavailable, using WASM with q8 quantization");
1635
+ }
1636
+
1637
+ state.model = await AutoModelForCausalLM.from_pretrained(modelId, {
1638
+ ...modelConfig,
1639
+ progress_callback: (progress) => {
1640
+ if (!progress) return;
1641
+
1642
+ // Handle different progress callback formats
1643
+ if (progress.status === 'progress' || (progress.loaded && progress.total)) {
1644
+ const loaded = progress.loaded || 0;
1645
+ const total = progress.total || 1;
1646
+ const percent = 40 + Math.round((loaded / total) * 55);
1647
+ progressBar.style.width = `${percent}%`;
1648
+
1649
+ if (progress.cached) {
1650
+ modelCached = true;
1651
+ loaderText.textContent = `LOADING FROM CACHE (${percent}%)`;
1652
+ loaderSubtext.innerHTML = `Loading model from browser cache...<br><span style="color: var(--success); font-size: 0.85rem;">${percent}% complete</span>`;
1653
+ } else {
1654
+ loaderText.textContent = `DOWNLOADING MODEL (${percent}%)`;
1655
+ loaderSubtext.innerHTML = `Downloading model weights...<br><span style="color: var(--text-muted); font-size: 0.85rem;">${percent}% complete</span>`;
1656
+ }
1657
+ } else if (progress.status === 'done' || progress.file) {
1658
+ progressBar.style.width = '98%';
1659
+ if (progress.cached || modelCached) {
1660
+ loaderText.textContent = 'MODEL READY (CACHED)';
1661
+ loaderSubtext.innerHTML = 'βœ“ Model loaded from cache<br><span style="color: var(--success); font-size: 0.85rem;">Subsequent loads will be instant!</span>';
1662
+ console.log("βœ“ Model loaded from cache");
1663
+ } else {
1664
+ loaderText.textContent = 'MODEL READY (DOWNLOADED)';
1665
+ loaderSubtext.innerHTML = 'βœ“ Model downloaded and cached<br><span style="color: var(--success); font-size: 0.85rem;">Ready for next time!</span>';
1666
+ console.log("βœ“ Model downloaded and cached");
1667
+ }
1668
+ }
1669
+ }
1670
+ });
1671
+
1672
+ // Step 4: Complete
1673
+ progressBar.style.width = '100%';
1674
+ const loadTime = ((performance.now() - loadStartTime) / 1000).toFixed(1);
1675
+ loaderText.textContent = 'SYSTEM READY';
1676
+ loaderSubtext.innerHTML = `βœ“ Model initialization complete<br><span style="color: var(--success); font-size: 0.85rem;">Loaded in ${loadTime}s β€’ Ready to test functions!</span>`;
1677
+ await new Promise(r => setTimeout(r, 300));
1678
+
1679
+ // Store model info in state
1680
+ state.modelInfo = {
1681
+ name: "functiongemma-270m",
1682
+ version: "it-ONNX",
1683
+ quantization: modelConfig.dtype || 'fp32',
1684
+ device: modelConfig.device || 'default'
1685
+ };
1686
+
1687
+ // Track model load in telemetry
1688
+ telemetry.trackModelLoad(parseFloat(loadTime));
1689
+ telemetry.trackCacheHit(modelCached || tokenizerCached);
1690
+
1691
+ // Cache model metadata in IndexedDB
1692
+ try {
1693
+ const db = await dbPromise;
1694
+ await db.put('models', {
1695
+ modelId: modelId,
1696
+ config: modelConfig,
1697
+ loadTime: loadTime,
1698
+ cached: modelCached || tokenizerCached,
1699
+ timestamp: Date.now()
1700
+ }, 'current-model');
1701
+ } catch (e) {
1702
+ console.log('IndexedDB cache write failed:', e);
1703
+ }
1704
+
1705
+ console.log("βœ“ Model loaded successfully");
1706
+ console.log(` Device: ${modelConfig.device || 'default'}`);
1707
+ console.log(` Quantization: ${modelConfig.dtype || 'fp32'}`);
1708
+ console.log(` Cached: ${modelCached || tokenizerCached ? 'Yes' : 'No (first load)'}`);
1709
+ console.log(` Load time: ${loadTime}s`);
1710
+
1711
+ // Update model info display
1712
+ updateModelInfoDisplay();
1713
+
1714
+ // Setup cache warmup after model is loaded
1715
+ setupCacheWarmup();
1716
+
1717
+ } catch(e) {
1718
+ console.error("Model load failed:", e);
1719
+ loaderText.textContent = '⚠ LOAD FAILED';
1720
+ loaderSubtext.innerHTML = `Error: ${e.message}<br><span style="color: var(--alert); font-size: 0.85rem;">Please refresh the page</span>`;
1721
+ progressBar.style.background = 'var(--alert)';
1722
+
1723
+ // If WebGPU failed, try fallback
1724
+ if (e.message && e.message.includes('webgpu') && hasWebGPU) {
1725
+ console.log("⚠ WebGPU failed, retrying with WASM fallback...");
1726
+ loaderText.textContent = 'RETRYING WITH CPU';
1727
+ loaderSubtext.innerHTML = 'WebGPU failed, falling back to CPU...<br><span style="color: var(--text-muted); font-size: 0.85rem;">This may be slower</span>';
1728
+ progressBar.style.background = 'var(--acid)';
1729
+
1730
+ try {
1731
+ state.model = await AutoModelForCausalLM.from_pretrained(modelId, {
1732
+ dtype: "q8",
1733
+ device: "wasm"
1734
+ });
1735
+ progressBar.style.width = '100%';
1736
+ progressBar.style.background = 'var(--acid)';
1737
+ loaderText.textContent = 'SYSTEM READY (CPU)';
1738
+ loaderSubtext.innerHTML = 'βœ“ Model loaded with CPU fallback<br><span style="color: var(--success); font-size: 0.85rem;">Ready to test functions!</span>';
1739
+ console.log("βœ“ Model loaded with CPU fallback");
1740
+
1741
+ // Store model info in state for fallback
1742
+ state.modelInfo = {
1743
+ name: "functiongemma-270m",
1744
+ version: "it-ONNX",
1745
+ quantization: "q8",
1746
+ device: "wasm"
1747
+ };
1748
+ updateModelInfoDisplay();
1749
+ } catch(fallbackError) {
1750
+ console.error("Fallback also failed:", fallbackError);
1751
+ throw fallbackError;
1752
+ }
1753
+ } else {
1754
+ throw e;
1755
+ }
1756
+ } finally {
1757
+ // Hide loader after a brief delay
1758
+ setTimeout(() => {
1759
+ loader.classList.add('hidden');
1760
+ }, 500);
1761
+ }
1762
+ }
1763
+
1764
+ // Render Level
1765
+ function renderLevel() {
1766
+ const lvl = levels[state.levelIdx];
1767
+
1768
+ document.getElementById('lvlName').innerText = lvl.title;
1769
+ document.getElementById('lvlDesc').innerText = lvl.description;
1770
+ document.getElementById('objectiveBadge').innerText = `LEVEL ${lvl.id} // ${lvl.difficulty}`;
1771
+ document.getElementById('levelDisp').innerText = `0${lvl.id}`;
1772
+ document.getElementById('scenarioCount').innerText = lvl.scenarios.length;
1773
+ document.getElementById('scoreDisp').innerText = state.totalScore.toString().padStart(4, '0');
1774
+
1775
+ // Render scenarios
1776
+ const grid = document.getElementById('scenarioGrid');
1777
+ grid.innerHTML = '';
1778
+ lvl.scenarios.forEach((s, i) => {
1779
+ const div = document.createElement('div');
1780
+ div.className = 'scenario-card';
1781
+ div.innerHTML = `
1782
+ <div class="scenario-icon">${s.icon}</div>
1783
+ <div class="scenario-text">${s.query}</div>
1784
+ <div class="scenario-expected ${s.shouldTrigger ? 'should-trigger' : 'should-not'}">
1785
+ ${s.shouldTrigger ? 'βœ“ SHOULD' : 'βœ— SHOULD NOT'}
1786
+ </div>
1787
+ `;
1788
+ div.style.opacity = '0';
1789
+ div.style.transform = 'translateY(10px)';
1790
+ grid.appendChild(div);
1791
+
1792
+ setTimeout(() => {
1793
+ div.style.transition = 'all 0.3s';
1794
+ div.style.opacity = '1';
1795
+ div.style.transform = 'translateY(0)';
1796
+ }, i * 50);
1797
+ });
1798
+
1799
+ // Render competitors
1800
+ const compList = document.getElementById('competitorList');
1801
+ if (lvl.competitors.length === 0) {
1802
+ compList.innerHTML = '<div style="color: var(--success);">No competitors this level!</div>';
1803
+ } else {
1804
+ compList.innerHTML = lvl.competitors.map(c => `
1805
+ <div class="competitor-fn">
1806
+ <div class="fn-name-display">${c.name}()</div>
1807
+ <div style="font-size: 0.75rem; color: #888;">${c.description}</div>
1808
+ </div>
1809
+ `).join('');
1810
+ }
1811
+
1812
+ // Update achievements display
1813
+ const achievementsList = document.getElementById('achievementsList');
1814
+ if (achievementsList) {
1815
+ if (state.achievements.length === 0) {
1816
+ achievementsList.innerHTML = '<div style="color: #666;">No achievements yet...</div>';
1817
+ } else {
1818
+ const names = {
1819
+ 'first_perfect': 'πŸ† Perfect Score',
1820
+ 'streak_3': 'πŸ”₯ On Fire!',
1821
+ 'streak_5': '⚑ Unstoppable!',
1822
+ 'score_1k': 'πŸ’― Score Master',
1823
+ 'all_levels': 'πŸ‘‘ Master Engineer'
1824
+ };
1825
+ achievementsList.innerHTML = state.achievements.map(a =>
1826
+ `<div style="color: var(--acid); margin-bottom: 5px;">${names[a] || a}</div>`
1827
+ ).join('');
1828
+ }
1829
+ }
1830
+
1831
+ // Update streak display
1832
+ updateStreakDisplay();
1833
+
1834
+ // Set starter code
1835
+ document.getElementById('functionEditor').value = JSON.stringify(lvl.starterCode, null, 2);
1836
+ validateJSON();
1837
+ // Ensure textarea shows full content
1838
+ setTimeout(autoResizeTextarea, 100);
1839
+
1840
+ // Reset result panel
1841
+ document.getElementById('resultPanel').classList.remove('visible');
1842
+ document.getElementById('nextBtn').style.display = 'none';
1843
+ }
1844
+
1845
+ // JSON Validation and Auto-resize
1846
+ const functionEditor = document.getElementById('functionEditor');
1847
+ functionEditor.addEventListener('input', () => {
1848
+ validateJSON();
1849
+ autoResizeTextarea();
1850
+ });
1851
+
1852
+ function getStandardTemplate() {
1853
+ // Return template with helpful comments (as JSON comments aren't valid, we'll add them in a special way)
1854
+ return {
1855
+ name: "function_name",
1856
+ description: "Clear, descriptive description of what this function does. Mention key operations and purpose.",
1857
+ parameters: {
1858
+ type: "object",
1859
+ properties: {
1860
+ param_1: {
1861
+ type: "string",
1862
+ description: "Description of the first parameter"
1863
+ },
1864
+ param_2: {
1865
+ type: "string",
1866
+ enum: ["option_a", "option_b", "option_c"],
1867
+ description: "Parameter with fixed options (remove enum if not needed)"
1868
+ },
1869
+ param_3: {
1870
+ type: "number",
1871
+ description: "Numeric parameter (use number for amounts, counts, etc)"
1872
+ }
1873
+ },
1874
+ required: ["param_1", "param_2"]
1875
+ },
1876
+ _hints: [
1877
+ "πŸ’‘ TIP: Function name should be specific and descriptive",
1878
+ "πŸ’‘ TIP: Description is KEY - AI reads this to decide when to call your function",
1879
+ "πŸ’‘ TIP: Use enums to constrain values and make intent clearer",
1880
+ "πŸ’‘ TIP: Required fields help the model understand what's essential",
1881
+ "πŸ’‘ TIP: Be explicit about what your function does AND what it doesn't do"
1882
+ ]
1883
+ };
1884
+ }
1885
+
1886
+ function getTemplateWithComments() {
1887
+ return `{
1888
+ // πŸ’‘ FUNCTION DEFINITION TEMPLATE WITH HINTS
1889
+ // ===========================================
1890
+ //
1891
+ // πŸ’‘ TIP: Function name should be specific and descriptive
1892
+ // Good: "get_weather", "create_task", "process_payment"
1893
+ // Bad: "do_stuff", "function1", "data"
1894
+ //
1895
+ "name": "function_name",
1896
+
1897
+ // πŸ’‘ TIP: Description is THE MOST IMPORTANT part!
1898
+ // - AI reads this to decide when to call your function
1899
+ // - Be specific about what it does
1900
+ // - Mention key operations (get, create, process, etc.)
1901
+ // - Explicitly exclude what it doesn't do (if needed)
1902
+ //
1903
+ "description": "Clear, descriptive description of what this function does. Mention key operations and purpose.",
1904
+
1905
+ "parameters": {
1906
+ "type": "object",
1907
+ "properties": {
1908
+ // πŸ’‘ TIP: Parameter names should be clear and descriptive
1909
+ "param_1": {
1910
+ "type": "string",
1911
+ // πŸ’‘ TIP: Parameter description helps AI understand what to extract
1912
+ "description": "Description of the first parameter"
1913
+ },
1914
+
1915
+ // πŸ’‘ TIP: Use enums to constrain values - makes intent clearer!
1916
+ // Remove the enum array if you don't need fixed options
1917
+ "param_2": {
1918
+ "type": "string",
1919
+ "enum": ["option_a", "option_b", "option_c"],
1920
+ "description": "Parameter with fixed options (remove enum if not needed)"
1921
+ },
1922
+
1923
+ // πŸ’‘ TIP: Use "number" type for amounts, counts, quantities
1924
+ // Use "string" for text, IDs, names
1925
+ // Use "boolean" for true/false values
1926
+ "param_3": {
1927
+ "type": "number",
1928
+ "description": "Numeric parameter (use number for amounts, counts, etc)"
1929
+ }
1930
+ },
1931
+ // πŸ’‘ TIP: Required fields help the model understand what's essential
1932
+ // Only include parameters that are truly required
1933
+ "required": ["param_1", "param_2"]
1934
+ }
1935
+ }`;
1936
+ }
1937
+
1938
+ window.loadTemplate = function() {
1939
+ const editor = document.getElementById('functionEditor');
1940
+ // Use template with comments (as a string since JSON doesn't support comments)
1941
+ editor.value = getTemplateWithComments();
1942
+ // Try to validate (will fail due to comments, but that's okay)
1943
+ try {
1944
+ // Remove comments and validate
1945
+ const cleaned = editor.value.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
1946
+ const parsed = JSON.parse(cleaned);
1947
+ state.currentFunction = parsed;
1948
+ document.getElementById('jsonOutput').style.color = 'var(--success)';
1949
+ document.getElementById('jsonOutput').textContent = 'βœ“ Template loaded (comments are for reference only)';
1950
+ } catch(e) {
1951
+ document.getElementById('jsonOutput').style.color = 'var(--text-muted)';
1952
+ document.getElementById('jsonOutput').textContent = 'πŸ’‘ Template with hints loaded. Remove comments (// lines) before testing.';
1953
+ }
1954
+
1955
+ // Auto-resize to show full template
1956
+ setTimeout(autoResizeTextarea, 100);
1957
+ };
1958
+
1959
+ // Execution Logs
1960
+ function addLog(label, content, type = 'info') {
1961
+ const logsContent = document.getElementById('logsContent');
1962
+
1963
+ // Clear placeholder if it exists
1964
+ if (logsContent.innerHTML.includes('Logs will appear here')) {
1965
+ logsContent.innerHTML = '';
1966
+ }
1967
+
1968
+ const entry = document.createElement('div');
1969
+ entry.className = 'log-entry';
1970
+
1971
+ const time = new Date().toLocaleTimeString();
1972
+ let html = `<div class="log-time">[${time}]</div>`;
1973
+ html += `<span class="log-label">${label}</span>`;
1974
+
1975
+ if (type === 'prompt') {
1976
+ html += `<div class="log-prompt"><strong>πŸ“ PROMPT:</strong><br>"${content}"</div>`;
1977
+ } else if (type === 'tools') {
1978
+ html += `<div class="log-tools"><strong>πŸ”§ AVAILABLE FUNCTIONS:</strong><br>${content}</div>`;
1979
+ } else if (type === 'triggered') {
1980
+ html += `<span class="log-success">βœ… ${content}</span>`;
1981
+ } else if (type === 'error') {
1982
+ html += `<span class="log-error">❌ ${content}</span>`;
1983
+ } else {
1984
+ html += `<span class="log-${type}">${content}</span>`;
1985
+ }
1986
+
1987
+ entry.innerHTML = html;
1988
+ logsContent.insertBefore(entry, logsContent.firstChild);
1989
+ logsContent.scrollTop = 0;
1990
+ }
1991
+
1992
+ window.clearLogs = function() {
1993
+ const logsContent = document.getElementById('logsContent');
1994
+ logsContent.innerHTML = '<div style="color: #666; text-align: center; padding: 40px 20px;">Logs cleared. Run a test to see new logs...</div>';
1995
+ };
1996
+
1997
+ // Auto-resize textarea to fit content
1998
+ function autoResizeTextarea() {
1999
+ const editor = document.getElementById('functionEditor');
2000
+ if (editor) {
2001
+ // Reset height to auto to get the correct scrollHeight
2002
+ editor.style.height = 'auto';
2003
+ // Set height to scrollHeight, but ensure minimum height
2004
+ const newHeight = Math.max(300, editor.scrollHeight);
2005
+ editor.style.height = newHeight + 'px';
2006
+ }
2007
+ }
2008
+
2009
+ function validateJSON() {
2010
+ const editor = document.getElementById('functionEditor');
2011
+ const output = document.getElementById('jsonOutput');
2012
+
2013
+ try {
2014
+ // Remove comments before parsing
2015
+ let cleaned = editor.value
2016
+ .replace(/\/\/.*$/gm, '') // Remove single-line comments
2017
+ .replace(/\/\*[\s\S]*?\*\//g, ''); // Remove multi-line comments
2018
+
2019
+ const parsed = JSON.parse(cleaned);
2020
+ state.currentFunction = parsed;
2021
+
2022
+ // Validate required fields
2023
+ if (!parsed.name || !parsed.description) {
2024
+ throw new Error("Missing 'name' or 'description'");
2025
+ }
2026
+
2027
+ // Additional validation
2028
+ const warnings = [];
2029
+ if (parsed.name === 'function_name' || parsed.name.includes('_name')) {
2030
+ warnings.push('⚠️ Consider changing the function name to something specific');
2031
+ }
2032
+ if (parsed.description.length < 20) {
2033
+ warnings.push('⚠️ Description seems short - be more descriptive!');
2034
+ }
2035
+ if (parsed.description === 'Clear, descriptive description of what this function does. Mention key operations and purpose.') {
2036
+ warnings.push('⚠️ Update the description to match your function!');
2037
+ }
2038
+
2039
+ output.style.color = 'var(--success)';
2040
+ let outputText = 'βœ“ Valid JSON Schema\n\n' + JSON.stringify(parsed, null, 2);
2041
+ if (warnings.length > 0) {
2042
+ outputText = warnings.join('\n') + '\n\n' + outputText;
2043
+ output.style.color = 'var(--acid)';
2044
+ }
2045
+ output.textContent = outputText;
2046
+ } catch(e) {
2047
+ output.style.color = 'var(--alert)';
2048
+ output.textContent = `βœ— JSON Error: ${e.message}\n\nπŸ’‘ Tip: Remove any comments (// lines) before testing.\nπŸ’‘ Tip: Make sure all strings are in double quotes.`;
2049
+ state.currentFunction = null;
2050
+ }
2051
+
2052
+ // Auto-resize textarea after validation
2053
+ autoResizeTextarea();
2054
+ }
2055
+
2056
+ // Test Function
2057
+ window.testFunction = async function() {
2058
+ if (!state.currentFunction) {
2059
+ alert('Please fix your JSON first!');
2060
+ return;
2061
+ }
2062
+
2063
+ const btn = document.getElementById('testBtn');
2064
+ const testLoader = document.getElementById('testLoader');
2065
+ const testProgress = document.getElementById('testProgress');
2066
+
2067
+ btn.disabled = true;
2068
+ btn.innerText = 'TESTING...';
2069
+
2070
+ // Show loader after a brief delay to avoid flickering
2071
+ let loaderTimeout = setTimeout(() => {
2072
+ testLoader.classList.add('visible');
2073
+ testProgress.textContent = 'Preparing test environment...';
2074
+ }, 100);
2075
+
2076
+ try {
2077
+ const lvl = levels[state.levelIdx];
2078
+ const results = [];
2079
+ let score = 0;
2080
+ const totalScenarios = lvl.scenarios.length;
2081
+
2082
+ // Build tools array
2083
+ const tools = [
2084
+ {
2085
+ type: "function",
2086
+ function: state.currentFunction
2087
+ },
2088
+ ...lvl.competitors.map(c => ({
2089
+ type: "function",
2090
+ function: c
2091
+ }))
2092
+ ];
2093
+
2094
+ // Log function definitions
2095
+ addLog('FUNCTION_SCHEMA', 'Loaded function definitions for testing', 'info');
2096
+ const toolsHtml = tools.map(t =>
2097
+ `<div class="log-function"><span class="log-function-name">${t.function.name}</span>() - ${t.function.description}</div>`
2098
+ ).join('');
2099
+ addLog('AVAILABLE_TOOLS', toolsHtml, 'tools');
2100
+
2101
+ // Test each scenario
2102
+ for (let i = 0; i < lvl.scenarios.length; i++) {
2103
+ const scenario = lvl.scenarios[i];
2104
+ const scenarioNum = i + 1;
2105
+ testProgress.textContent = `Testing scenario ${scenarioNum}/${totalScenarios}: "${scenario.query.substring(0, 40)}${scenario.query.length > 40 ? '...' : ''}"`;
2106
+ addLog('TEST_START', `Testing scenario: "${scenario.query}"`, 'info');
2107
+
2108
+ const messages = [
2109
+ { role: "developer", content: "You are a function calling model. Select the most appropriate function for the user's request." },
2110
+ { role: "user", content: scenario.query }
2111
+ ];
2112
+
2113
+ addLog('USER_PROMPT', scenario.query, 'prompt');
2114
+
2115
+ try {
2116
+ const inputs = await state.tokenizer.apply_chat_template(messages, {
2117
+ tools: tools,
2118
+ tokenize: true,
2119
+ add_generation_prompt: true,
2120
+ return_dict: true
2121
+ });
2122
+
2123
+ const genStart = performance.now();
2124
+ const output = await state.model.generate({
2125
+ ...inputs,
2126
+ max_new_tokens: 128,
2127
+ do_sample: false
2128
+ });
2129
+ const genTime = performance.now() - genStart;
2130
+
2131
+ // Track generation time in telemetry
2132
+ telemetry.trackGeneration(genTime);
2133
+
2134
+ const decoded = await state.tokenizer.decode(output.slice(0, [inputs.input_ids.dims[1], null]), { skip_special_tokens: false });
2135
+
2136
+ addLog('MODEL_OUTPUT', `Generated in ${genTime.toFixed(0)}ms`, 'info');
2137
+ addLog('RAW_OUTPUT', decoded.substring(0, 200) + (decoded.length > 200 ? '...' : ''), 'info');
2138
+
2139
+ // Extract triggered function - improved pattern matching
2140
+ let triggered = null;
2141
+
2142
+ // Pattern 1: call:function_name
2143
+ const match1 = decoded.match(/call:\s*"?(\w+)"?/i);
2144
+ if (match1) triggered = match1[1];
2145
+
2146
+ // Pattern 2: function: "function_name" or function: function_name
2147
+ if (!triggered) {
2148
+ const match2 = decoded.match(/function:\s*"?(\w+)"?/i);
2149
+ if (match2) triggered = match2[1];
2150
+ }
2151
+
2152
+ // Pattern 3: "name": "function_name"
2153
+ if (!triggered) {
2154
+ const match3 = decoded.match(/"name"\s*:\s*"(\w+)"/i);
2155
+ if (match3) triggered = match3[1];
2156
+ }
2157
+
2158
+ // Pattern 4: <tool_call> or similar XML-like tags
2159
+ if (!triggered) {
2160
+ const match4 = decoded.match(/<tool_call[^>]*>[\s\S]*?name["\s:=]+(\w+)/i);
2161
+ if (match4) triggered = match4[1];
2162
+ }
2163
+
2164
+ // Pattern 5: Look for function names from available tools
2165
+ if (!triggered) {
2166
+ const allFunctionNames = tools.map(t => t.function.name);
2167
+ for (const fnName of allFunctionNames) {
2168
+ // Check if function name appears in decoded output in a meaningful context
2169
+ const regex = new RegExp(`\\b${fnName}\\b`, 'i');
2170
+ if (regex.test(decoded)) {
2171
+ // Additional check: make sure it's not just in a description
2172
+ const contextMatch = decoded.match(new RegExp(`(call|function|name|invoke|use|select)[\\s:="]*${fnName}`, 'i'));
2173
+ if (contextMatch) {
2174
+ triggered = fnName;
2175
+ break;
2176
+ }
2177
+ }
2178
+ }
2179
+ }
2180
+
2181
+ addLog('FUNCTION_DETECTED', triggered ? triggered : 'NONE', triggered ? 'triggered' : 'error');
2182
+
2183
+ const triggeredYourFunction = triggered === state.currentFunction.name;
2184
+ const shouldHaveTriggered = scenario.shouldTrigger;
2185
+
2186
+ // Evaluate result
2187
+ let result = {
2188
+ query: scenario.query,
2189
+ expected: shouldHaveTriggered,
2190
+ triggered: triggeredYourFunction,
2191
+ correctFunction: triggered,
2192
+ icon: scenario.icon
2193
+ };
2194
+
2195
+ if (shouldHaveTriggered && triggeredYourFunction) {
2196
+ // Correct positive
2197
+ result.status = 'correct';
2198
+ result.points = 100;
2199
+ score += 100;
2200
+ addLog('RESULT_EVAL', `βœ… CORRECT! Expected: ${state.currentFunction.name}, Got: ${triggered}`, 'success');
2201
+ } else if (!shouldHaveTriggered && !triggeredYourFunction) {
2202
+ // Correct negative
2203
+ result.status = 'correct';
2204
+ result.points = 50;
2205
+ score += 50;
2206
+ addLog('RESULT_EVAL', `βœ… CORRECT! Expected: NO TRIGGER, Got: NONE`, 'success');
2207
+ } else if (shouldHaveTriggered && !triggeredYourFunction) {
2208
+ // False negative (missed trigger)
2209
+ result.status = 'wrong';
2210
+ result.points = 0;
2211
+ addLog('RESULT_EVAL', `❌ WRONG! Expected: ${state.currentFunction.name}, Got: ${triggered || 'NONE'}`, 'error');
2212
+ } else {
2213
+ // False positive (wrong trigger)
2214
+ result.status = 'wrong';
2215
+ result.points = -50;
2216
+ score -= 50;
2217
+ addLog('RESULT_EVAL', `❌ WRONG! Expected: NO TRIGGER, Got: ${triggered || 'NONE'}`, 'error');
2218
+ }
2219
+
2220
+ results.push(result);
2221
+
2222
+ // Update scenario card visual feedback
2223
+ const scenarioCards = document.querySelectorAll('.scenario-card');
2224
+ if (scenarioCards[i]) {
2225
+ scenarioCards[i].classList.add(result.status);
2226
+ setTimeout(() => {
2227
+ scenarioCards[i].classList.remove(result.status);
2228
+ }, 2000);
2229
+ }
2230
+
2231
+ } catch(e) {
2232
+ console.error('Test error:', e);
2233
+ addLog('ERROR', `${e.message}`, 'error');
2234
+ results.push({
2235
+ query: scenario.query,
2236
+ status: 'error',
2237
+ points: 0,
2238
+ icon: scenario.icon
2239
+ });
2240
+ }
2241
+ }
2242
+
2243
+ // Display results
2244
+ displayResults(results, score);
2245
+ } catch(error) {
2246
+ console.error('Test function error:', error);
2247
+ addLog('FATAL_ERROR', `Test failed: ${error.message}`, 'error');
2248
+ } finally {
2249
+ // Always hide loader and re-enable button
2250
+ clearTimeout(loaderTimeout);
2251
+ testLoader.classList.remove('visible');
2252
+ btn.disabled = false;
2253
+ btn.innerText = 'TEST FUNCTION';
2254
+ }
2255
+ };
2256
+
2257
+ function displayResults(results, score) {
2258
+ const panel = document.getElementById('resultPanel');
2259
+ const title = document.getElementById('resultTitle');
2260
+ const scoreEl = document.getElementById('resultScore');
2261
+ const breakdown = document.getElementById('resultBreakdown');
2262
+
2263
+ const passed = results.every(r => r.status === 'correct');
2264
+
2265
+ title.innerText = passed ? 'βœ“ ALL TESTS PASSED' : 'βœ— SOME TESTS FAILED';
2266
+ title.className = `result-title ${passed ? 'success' : 'fail'}`;
2267
+
2268
+ scoreEl.innerText = (score >= 0 ? '+' : '') + score;
2269
+ scoreEl.className = `result-score ${score >= 0 ? 'success' : 'fail'}`;
2270
+
2271
+ breakdown.innerHTML = results.map(r => `
2272
+ <div class="result-item ${r.status}">
2273
+ <span>${r.icon} ${r.query}</span>
2274
+ <span style="font-weight: bold;">
2275
+ ${r.points >= 0 ? '+' : ''}${r.points} pts
2276
+ ${r.correctFunction && r.correctFunction !== 'null' ? ` (${r.correctFunction})` : ''}
2277
+ </span>
2278
+ </div>
2279
+ `).join('');
2280
+
2281
+ panel.classList.add('visible');
2282
+
2283
+ // Always show next button - users can proceed even if they failed
2284
+ document.getElementById('nextBtn').style.display = 'block';
2285
+
2286
+ if (passed) {
2287
+ // Only add score if they passed
2288
+ state.totalScore += score;
2289
+ document.getElementById('scoreDisp').innerText = state.totalScore.toString().padStart(4, '0');
2290
+
2291
+ // Track level completion in telemetry
2292
+ telemetry.trackLevelComplete(state.levelIdx + 1, score, true);
2293
+
2294
+ // Check achievements
2295
+ checkAchievements(results, score);
2296
+
2297
+ // Update streak display
2298
+ updateStreakDisplay();
2299
+ } else {
2300
+ // On failure, don't add score but allow progression
2301
+ state.streak = 0;
2302
+ updateStreakDisplay();
2303
+
2304
+ // Track level completion (failed) in telemetry
2305
+ telemetry.trackLevelComplete(state.levelIdx + 1, score, false);
2306
+ }
2307
+
2308
+ // Setup cache warmup for next level
2309
+ setupCacheWarmup();
2310
+
2311
+ // Calculate accuracy
2312
+ const totalScenarios = (state.levelIdx + 1) * levels[state.levelIdx].scenarios.length;
2313
+ const accuracy = totalScenarios > 0 ? Math.round((state.totalScore / (totalScenarios * 100)) * 100) : 0;
2314
+ document.getElementById('accuracyDisp').innerText = `${accuracy}%`;
2315
+
2316
+ // Track level attempts
2317
+ const levelKey = `level_${state.levelIdx + 1}`;
2318
+ state.levelAttempts[levelKey] = (state.levelAttempts[levelKey] || 0) + 1;
2319
+ saveState();
2320
+ }
2321
+
2322
+ // Next Level
2323
+ window.nextLevel = function() {
2324
+ state.levelIdx++;
2325
+ if (state.levelIdx >= levels.length) {
2326
+ victory();
2327
+ } else {
2328
+ clearLogs();
2329
+ renderLevel();
2330
+
2331
+ // Track level navigation
2332
+ if (typeof gtag !== 'undefined') {
2333
+ gtag('event', 'level_navigation', {
2334
+ level_id: state.levelIdx + 1,
2335
+ action: 'next'
2336
+ });
2337
+ }
2338
+ }
2339
+ };
2340
+
2341
+ // Reset Editor
2342
+ window.resetEditor = function() {
2343
+ const lvl = levels[state.levelIdx];
2344
+ document.getElementById('functionEditor').value = JSON.stringify(lvl.starterCode, null, 2);
2345
+ validateJSON();
2346
+ setTimeout(autoResizeTextarea, 100);
2347
+ };
2348
+
2349
+ // Check and unlock achievements
2350
+ function checkAchievements(results, score) {
2351
+ const newAchievements = [];
2352
+
2353
+ // First perfect score
2354
+ if (results.every(r => r.status === 'correct') && !state.achievements.includes('first_perfect')) {
2355
+ newAchievements.push({ id: 'first_perfect', name: 'Perfect Score', desc: 'Got all scenarios correct!' });
2356
+ state.achievements.push('first_perfect');
2357
+ }
2358
+
2359
+ // Streak achievements
2360
+ if (results.every(r => r.status === 'correct')) {
2361
+ state.streak++;
2362
+ if (state.streak > state.bestStreak) {
2363
+ state.bestStreak = state.streak;
2364
+ }
2365
+ if (state.streak === 3 && !state.achievements.includes('streak_3')) {
2366
+ newAchievements.push({ id: 'streak_3', name: 'On Fire!', desc: '3 perfect levels in a row!' });
2367
+ state.achievements.push('streak_3');
2368
+ }
2369
+ if (state.streak === 5 && !state.achievements.includes('streak_5')) {
2370
+ newAchievements.push({ id: 'streak_5', name: 'Unstoppable!', desc: '5 perfect levels in a row!' });
2371
+ state.achievements.push('streak_5');
2372
+ }
2373
+ } else {
2374
+ state.streak = 0;
2375
+ }
2376
+
2377
+ // Score milestones
2378
+ if (state.totalScore >= 1000 && !state.achievements.includes('score_1k')) {
2379
+ newAchievements.push({ id: 'score_1k', name: 'Score Master', desc: 'Reached 1000 points!' });
2380
+ state.achievements.push('score_1k');
2381
+ }
2382
+
2383
+ // Level completion
2384
+ if (state.levelIdx === 4 && !state.achievements.includes('all_levels')) {
2385
+ newAchievements.push({ id: 'all_levels', name: 'Master Engineer', desc: 'Completed all levels!' });
2386
+ state.achievements.push('all_levels');
2387
+ }
2388
+
2389
+ // Show new achievements
2390
+ if (newAchievements.length > 0) {
2391
+ showAchievementNotification(newAchievements);
2392
+ }
2393
+
2394
+ saveState();
2395
+ }
2396
+
2397
+ function showAchievementNotification(achievements) {
2398
+ achievements.forEach((ach, idx) => {
2399
+ setTimeout(() => {
2400
+ const notif = document.createElement('div');
2401
+ notif.style.cssText = `
2402
+ position: fixed;
2403
+ top: 20px;
2404
+ right: 20px;
2405
+ background: var(--panel);
2406
+ border: 3px solid var(--acid);
2407
+ padding: 20px;
2408
+ z-index: 10002;
2409
+ animation: slideInRight 0.5s var(--ease-out-expo);
2410
+ box-shadow: var(--hard-shadow);
2411
+ max-width: 300px;
2412
+ `;
2413
+ notif.innerHTML = `
2414
+ <div style="color: var(--acid); font-weight: bold; margin-bottom: 5px;">πŸ† ACHIEVEMENT UNLOCKED</div>
2415
+ <div style="font-size: 1.2rem; margin-bottom: 5px;">${ach.name}</div>
2416
+ <div style="color: var(--text-muted); font-size: 0.9rem;">${ach.desc}</div>
2417
+ `;
2418
+ document.body.appendChild(notif);
2419
+
2420
+ setTimeout(() => {
2421
+ notif.style.animation = 'slideOutRight 0.5s var(--ease-out-expo)';
2422
+ setTimeout(() => notif.remove(), 500);
2423
+ }, 3000);
2424
+ }, idx * 400);
2425
+ });
2426
+ }
2427
+
2428
+
2429
+ function updateStreakDisplay() {
2430
+ // Update streak in sidebar if element exists
2431
+ const streakEl = document.getElementById('streakDisp');
2432
+ if (streakEl) {
2433
+ streakEl.innerText = state.streak;
2434
+ streakEl.parentElement.style.display = state.streak > 0 ? 'flex' : 'none';
2435
+ }
2436
+ }
2437
+
2438
+ function updateModelInfoDisplay() {
2439
+ const modelInfoEl = document.getElementById('modelInfo');
2440
+ if (modelInfoEl && state.modelInfo.name) {
2441
+ document.getElementById('modelName').textContent = state.modelInfo.name;
2442
+ document.getElementById('modelVersion').textContent = state.modelInfo.version || '-';
2443
+ document.getElementById('modelQuantization').textContent = state.modelInfo.quantization.toUpperCase();
2444
+ document.getElementById('modelDevice').textContent = state.modelInfo.device.toUpperCase();
2445
+ modelInfoEl.style.display = 'block';
2446
+ }
2447
+ }
2448
+
2449
+ // Hints
2450
+ window.toggleHints = function() {
2451
+ const panel = document.getElementById('hintPanel');
2452
+ const content = document.getElementById('hintContent');
2453
+ const lvl = levels[state.levelIdx];
2454
+
2455
+ if (panel.classList.contains('visible')) {
2456
+ panel.classList.remove('visible');
2457
+ } else {
2458
+ state.hintsUsed++;
2459
+ saveState();
2460
+ content.innerHTML = lvl.hints.map((h, i) =>
2461
+ `<p style="margin-bottom: 8px;"><strong>${i+1}.</strong> ${h}</p>`
2462
+ ).join('');
2463
+ panel.classList.add('visible');
2464
+ }
2465
+ };
2466
+
2467
+ // Rules Modal
2468
+ window.toggleRules = function() {
2469
+ const modal = document.getElementById('rulesModal');
2470
+ modal.classList.toggle('visible');
2471
+ };
2472
+
2473
+ // Reset Progress
2474
+ window.resetProgress = function() {
2475
+ if (!confirm('⚠️ WARNING: This will reset ALL progress, achievements, and scores!\n\nAre you sure?')) {
2476
+ return;
2477
+ }
2478
+
2479
+ state.totalScore = 0;
2480
+ state.achievements = [];
2481
+ state.streak = 0;
2482
+ state.bestStreak = 0;
2483
+ state.levelAttempts = {};
2484
+ state.hintsUsed = 0;
2485
+ state.levelIdx = 0;
2486
+
2487
+ localStorage.removeItem('functionArenaState');
2488
+
2489
+ // Update UI
2490
+ const scoreDisp = document.getElementById('scoreDisp');
2491
+ const accuracyDisp = document.getElementById('accuracyDisp');
2492
+ if (scoreDisp) scoreDisp.innerText = '0000';
2493
+ if (accuracyDisp) accuracyDisp.innerText = '--';
2494
+ updateStreakDisplay();
2495
+
2496
+ const achievementsList = document.getElementById('achievementsList');
2497
+ if (achievementsList) {
2498
+ achievementsList.innerHTML = '<div style="color: #666;">No achievements yet...</div>';
2499
+ }
2500
+
2501
+ // Reload level
2502
+ clearLogs();
2503
+ renderLevel();
2504
+
2505
+ alert('Progress reset! Starting fresh...');
2506
+ };
2507
+
2508
+ // Victory
2509
+ function victory() {
2510
+ const achievementsCount = state.achievements.length;
2511
+ const totalAttempts = Object.values(state.levelAttempts).reduce((a, b) => a + b, 0);
2512
+ const avgAttempts = totalAttempts / 5;
2513
+
2514
+ document.body.innerHTML = `
2515
+ <div style="height:100vh; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; background: var(--void); padding: 20px;">
2516
+ <div class="noise-overlay"></div>
2517
+ <div class="scanlines"></div>
2518
+
2519
+ <h1 style="font-size: 4rem; color: var(--acid); margin-bottom: 20px; font-family: 'Clash Display'; text-transform: uppercase; animation: pulse 2s infinite;">
2520
+ πŸ† TRAINING_COMPLETE πŸ†
2521
+ </h1>
2522
+
2523
+ <p style="font-family: 'Space Mono'; margin-bottom: 40px; color: var(--text-muted); font-size: 1.2rem;">
2524
+ You've mastered function definition engineering!
2525
+ </p>
2526
+
2527
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; max-width: 800px; width: 100%; margin-bottom: 40px;">
2528
+ <div style="border: 3px solid var(--success); padding: 20px; background: var(--panel);">
2529
+ <div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">FINAL SCORE</div>
2530
+ <div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--success);">${state.totalScore}</div>
2531
+ </div>
2532
+
2533
+ <div style="border: 3px solid var(--hyper-purple); padding: 20px; background: var(--panel);">
2534
+ <div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">ACHIEVEMENTS</div>
2535
+ <div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--hyper-purple);">${achievementsCount}/5</div>
2536
+ </div>
2537
+
2538
+ <div style="border: 3px solid var(--acid); padding: 20px; background: var(--panel);">
2539
+ <div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">BEST STREAK</div>
2540
+ <div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--acid);">${state.bestStreak}</div>
2541
+ </div>
2542
+ </div>
2543
+
2544
+ ${achievementsCount > 0 ? `
2545
+ <div style="max-width: 600px; margin-bottom: 40px; padding: 20px; background: var(--panel); border: 2px solid var(--acid);">
2546
+ <div style="color: var(--acid); font-weight: bold; margin-bottom: 15px; font-size: 1.1rem;">YOUR ACHIEVEMENTS</div>
2547
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;">
2548
+ ${state.achievements.map(a => {
2549
+ const names = {
2550
+ 'first_perfect': 'πŸ† Perfect Score',
2551
+ 'streak_3': 'πŸ”₯ On Fire!',
2552
+ 'streak_5': '⚑ Unstoppable!',
2553
+ 'score_1k': 'πŸ’― Score Master',
2554
+ 'all_levels': 'πŸ‘‘ Master Engineer'
2555
+ };
2556
+ return `<div style="color: var(--acid); padding: 10px; background: rgba(204,255,0,0.1);">${names[a] || a}</div>`;
2557
+ }).join('')}
2558
+ </div>
2559
+ </div>
2560
+ ` : ''}
2561
+
2562
+ <p style="max-width: 600px; line-height: 1.6; color: var(--text-muted); margin-bottom: 40px;">
2563
+ You now understand how to craft precise function definitions, use enums effectively,
2564
+ and distinguish your functions from competitors. Apply this knowledge to build better AI integrations!
2565
+ </p>
2566
+
2567
+ <div style="display: flex; gap: 20px; flex-wrap: wrap; justify-content: center;">
2568
+ <button onclick="location.reload()" style="background: var(--text-main); color: var(--void); padding: 20px 40px; font-family: 'Clash Display'; font-size: 1.2rem; border: none; cursor: pointer; font-weight: 700; transition: all 0.2s;">
2569
+ TRAIN AGAIN
2570
+ </button>
2571
+ <button onclick="resetProgress(); location.reload();" style="background: transparent; color: var(--alert); padding: 20px 40px; font-family: 'Clash Display'; font-size: 1.2rem; border: 2px solid var(--alert); cursor: pointer; font-weight: 700; transition: all 0.2s;">
2572
+ RESET & RESTART
2573
+ </button>
2574
+ </div>
2575
+ </div>
2576
+ `;
2577
+ }
2578
+
2579
+ // Initialize
2580
+ window.addEventListener('load', () => {
2581
+ loadState();
2582
+ bootSequence();
2583
+ });
2584
+
2585
+ // Save state periodically
2586
+ setInterval(() => {
2587
+ if (state.booted) {
2588
+ saveState();
2589
+ }
2590
+ }, 30000); // Every 30 seconds
2591
+ </script>
2592
+ </body>
2593
+ </html>