Aashish34 commited on
Commit
3117c7d
·
verified ·
1 Parent(s): 9de44c7

Upload 3 files

Browse files
ml-complete-all-topics/app.js ADDED
@@ -0,0 +1,2381 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Data
2
+ const data = {
3
+ linearRegression: [
4
+ { experience: 1, salary: 39.764 },
5
+ { experience: 2, salary: 48.900 },
6
+ { experience: 3, salary: 56.978 },
7
+ { experience: 4, salary: 68.290 },
8
+ { experience: 5, salary: 77.867 },
9
+ { experience: 6, salary: 85.022 }
10
+ ],
11
+ logistic: [
12
+ { height: 150, label: 0, prob: 0.2 },
13
+ { height: 160, label: 0, prob: 0.35 },
14
+ { height: 170, label: 0, prob: 0.5 },
15
+ { height: 180, label: 1, prob: 0.65 },
16
+ { height: 190, label: 1, prob: 0.8 },
17
+ { height: 200, label: 1, prob: 0.9 }
18
+ ],
19
+ svm: [
20
+ { label: 'A', x1: 2, x2: 7, class: 1 },
21
+ { label: 'B', x1: 3, x2: 8, class: 1 },
22
+ { label: 'C', x1: 4, x2: 7, class: 1 },
23
+ { label: 'D', x1: 6, x2: 2, class: -1 },
24
+ { label: 'E', x1: 7, x2: 3, class: -1 },
25
+ { label: 'F', x1: 8, x2: 2, class: -1 }
26
+ ],
27
+ knn: [
28
+ { x: 1, y: 2, class: 'orange' },
29
+ { x: 0.9, y: 1.7, class: 'orange' },
30
+ { x: 1.5, y: 2.5, class: 'orange' },
31
+ { x: 4, y: 5, class: 'yellow' },
32
+ { x: 4.2, y: 4.8, class: 'yellow' },
33
+ { x: 3.8, y: 5.2, class: 'yellow' }
34
+ ],
35
+ roc: [
36
+ { id: 'A', true_label: 1, score: 0.95 },
37
+ { id: 'B', true_label: 0, score: 0.70 },
38
+ { id: 'C', true_label: 1, score: 0.60 },
39
+ { id: 'D', true_label: 0, score: 0.40 },
40
+ { id: 'E', true_label: 1, score: 0.20 }
41
+ ]
42
+ };
43
+
44
+ // State
45
+ let state = {
46
+ slope: 7.5,
47
+ intercept: 32,
48
+ learningRate: 0.1,
49
+ gdIterations: [],
50
+ testPoint: { x: 2, y: 1 },
51
+ svm: {
52
+ w1: 1,
53
+ w2: 1,
54
+ b: -10,
55
+ C: 1,
56
+ kernel: 'linear',
57
+ kernelParam: 1,
58
+ training: {
59
+ w: [0, 0],
60
+ b: 0,
61
+ step: 0,
62
+ learningRate: 0.01,
63
+ isTraining: false
64
+ }
65
+ }
66
+ };
67
+
68
+ // Initialize collapsible sections
69
+ function initSections() {
70
+ const sections = document.querySelectorAll('.section');
71
+
72
+ sections.forEach(section => {
73
+ const header = section.querySelector('.section-header');
74
+ const toggle = section.querySelector('.section-toggle');
75
+ const body = section.querySelector('.section-body');
76
+
77
+ // Start with first section expanded
78
+ if (section.id === 'intro') {
79
+ body.classList.add('expanded');
80
+ toggle.classList.remove('collapsed');
81
+ } else {
82
+ toggle.classList.add('collapsed');
83
+ }
84
+
85
+ header.addEventListener('click', () => {
86
+ const isExpanded = body.classList.contains('expanded');
87
+
88
+ if (isExpanded) {
89
+ body.classList.remove('expanded');
90
+ toggle.classList.add('collapsed');
91
+ } else {
92
+ body.classList.add('expanded');
93
+ toggle.classList.remove('collapsed');
94
+
95
+ // Initialize visualizations when section opens
96
+ if (section.id === 'linear-regression') initLinearRegression();
97
+ if (section.id === 'gradient-descent') initGradientDescent();
98
+ if (section.id === 'logistic-regression') initLogistic();
99
+ if (section.id === 'svm') initSVM();
100
+ if (section.id === 'knn') initKNN();
101
+ if (section.id === 'model-evaluation') initModelEvaluation();
102
+ if (section.id === 'regularization') initRegularization();
103
+ if (section.id === 'bias-variance') initBiasVariance();
104
+ if (section.id === 'cross-validation') initCrossValidation();
105
+ if (section.id === 'preprocessing') initPreprocessing();
106
+ if (section.id === 'loss-functions') initLossFunctions();
107
+ }
108
+ });
109
+ });
110
+ }
111
+
112
+ // Smooth scroll for TOC links
113
+ function initTOCLinks() {
114
+ const links = document.querySelectorAll('.toc-link');
115
+
116
+ links.forEach(link => {
117
+ link.addEventListener('click', (e) => {
118
+ e.preventDefault();
119
+ const targetId = link.getAttribute('href').substring(1);
120
+ const target = document.getElementById(targetId);
121
+
122
+ if (target) {
123
+ // Remove active from all links
124
+ links.forEach(l => l.classList.remove('active'));
125
+ link.classList.add('active');
126
+
127
+ // Scroll to target
128
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
129
+
130
+ // Expand the section
131
+ const toggle = target.querySelector('.section-toggle');
132
+ const body = target.querySelector('.section-body');
133
+ body.classList.add('expanded');
134
+ toggle.classList.remove('collapsed');
135
+ }
136
+ });
137
+ });
138
+
139
+ // Update active link on scroll
140
+ let ticking = false;
141
+ window.addEventListener('scroll', () => {
142
+ if (!ticking) {
143
+ window.requestAnimationFrame(() => {
144
+ updateActiveLink();
145
+ ticking = false;
146
+ });
147
+ ticking = true;
148
+ }
149
+ });
150
+ }
151
+
152
+ function updateActiveLink() {
153
+ const sections = document.querySelectorAll('.section');
154
+ const scrollPos = window.scrollY + 100;
155
+
156
+ sections.forEach(section => {
157
+ const top = section.offsetTop;
158
+ const height = section.offsetHeight;
159
+ const id = section.getAttribute('id');
160
+
161
+ if (scrollPos >= top && scrollPos < top + height) {
162
+ document.querySelectorAll('.toc-link').forEach(link => {
163
+ link.classList.remove('active');
164
+ if (link.getAttribute('href') === '#' + id) {
165
+ link.classList.add('active');
166
+ }
167
+ });
168
+ }
169
+ });
170
+ }
171
+
172
+ // Linear Regression Visualization
173
+ function initLinearRegression() {
174
+ const canvas = document.getElementById('lr-canvas');
175
+ if (!canvas || canvas.dataset.initialized) return;
176
+ canvas.dataset.initialized = 'true';
177
+
178
+ const slopeSlider = document.getElementById('slope-slider');
179
+ const interceptSlider = document.getElementById('intercept-slider');
180
+ const slopeVal = document.getElementById('slope-val');
181
+ const interceptVal = document.getElementById('intercept-val');
182
+
183
+ if (slopeSlider) {
184
+ slopeSlider.addEventListener('input', (e) => {
185
+ state.slope = parseFloat(e.target.value);
186
+ slopeVal.textContent = state.slope.toFixed(1);
187
+ drawLinearRegression();
188
+ });
189
+ }
190
+
191
+ if (interceptSlider) {
192
+ interceptSlider.addEventListener('input', (e) => {
193
+ state.intercept = parseFloat(e.target.value);
194
+ interceptVal.textContent = state.intercept.toFixed(1);
195
+ drawLinearRegression();
196
+ });
197
+ }
198
+
199
+ drawLinearRegression();
200
+ }
201
+
202
+ function drawLinearRegression() {
203
+ const canvas = document.getElementById('lr-canvas');
204
+ if (!canvas) return;
205
+
206
+ const ctx = canvas.getContext('2d');
207
+ const width = canvas.width = canvas.offsetWidth;
208
+ const height = canvas.height = 400;
209
+
210
+ ctx.clearRect(0, 0, width, height);
211
+
212
+ const padding = 60;
213
+ const chartWidth = width - 2 * padding;
214
+ const chartHeight = height - 2 * padding;
215
+
216
+ const xMin = 0, xMax = 7;
217
+ const yMin = 0, yMax = 100;
218
+
219
+ // Draw axes
220
+ ctx.strokeStyle = '#2a3544';
221
+ ctx.lineWidth = 2;
222
+ ctx.beginPath();
223
+ ctx.moveTo(padding, padding);
224
+ ctx.lineTo(padding, height - padding);
225
+ ctx.lineTo(width - padding, height - padding);
226
+ ctx.stroke();
227
+
228
+ // Grid
229
+ ctx.strokeStyle = 'rgba(42, 53, 68, 0.3)';
230
+ ctx.lineWidth = 1;
231
+ for (let i = 0; i <= 5; i++) {
232
+ const x = padding + (chartWidth / 5) * i;
233
+ ctx.beginPath();
234
+ ctx.moveTo(x, padding);
235
+ ctx.lineTo(x, height - padding);
236
+ ctx.stroke();
237
+
238
+ const y = height - padding - (chartHeight / 5) * i;
239
+ ctx.beginPath();
240
+ ctx.moveTo(padding, y);
241
+ ctx.lineTo(width - padding, y);
242
+ ctx.stroke();
243
+ }
244
+
245
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
246
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
247
+
248
+ // Draw data points
249
+ ctx.fillStyle = '#6aa9ff';
250
+ data.linearRegression.forEach(point => {
251
+ const x = scaleX(point.experience);
252
+ const y = scaleY(point.salary);
253
+ ctx.beginPath();
254
+ ctx.arc(x, y, 6, 0, 2 * Math.PI);
255
+ ctx.fill();
256
+ });
257
+
258
+ // Draw regression line
259
+ ctx.strokeStyle = '#ff8c6a';
260
+ ctx.lineWidth = 3;
261
+ ctx.beginPath();
262
+ const y1 = state.slope * xMin + state.intercept;
263
+ const y2 = state.slope * xMax + state.intercept;
264
+ ctx.moveTo(scaleX(xMin), scaleY(y1));
265
+ ctx.lineTo(scaleX(xMax), scaleY(y2));
266
+ ctx.stroke();
267
+
268
+ // Labels
269
+ ctx.fillStyle = '#a9b4c2';
270
+ ctx.font = '12px sans-serif';
271
+ ctx.textAlign = 'center';
272
+ ctx.fillText('Experience (years)', width / 2, height - 20);
273
+ ctx.save();
274
+ ctx.translate(20, height / 2);
275
+ ctx.rotate(-Math.PI / 2);
276
+ ctx.fillText('Salary ($k)', 0, 0);
277
+ ctx.restore();
278
+
279
+ // Calculate MSE
280
+ let mse = 0;
281
+ data.linearRegression.forEach(point => {
282
+ const predicted = state.slope * point.experience + state.intercept;
283
+ const error = point.salary - predicted;
284
+ mse += error * error;
285
+ });
286
+ mse /= data.linearRegression.length;
287
+
288
+ // Display MSE
289
+ ctx.fillStyle = '#7ef0d4';
290
+ ctx.font = '14px sans-serif';
291
+ ctx.textAlign = 'right';
292
+ ctx.fillText(`MSE: ${mse.toFixed(2)}`, width - padding, padding + 20);
293
+ }
294
+
295
+ // Gradient Descent Visualization
296
+ function initGradientDescent() {
297
+ const canvas = document.getElementById('gd-canvas');
298
+ if (!canvas || canvas.dataset.initialized) return;
299
+ canvas.dataset.initialized = 'true';
300
+
301
+ const runBtn = document.getElementById('run-gd');
302
+ const resetBtn = document.getElementById('reset-gd');
303
+ const lrSlider = document.getElementById('lr-slider');
304
+ const lrVal = document.getElementById('lr-val');
305
+
306
+ if (lrSlider) {
307
+ lrSlider.addEventListener('input', (e) => {
308
+ state.learningRate = parseFloat(e.target.value);
309
+ lrVal.textContent = state.learningRate.toFixed(2);
310
+ });
311
+ }
312
+
313
+ if (runBtn) {
314
+ runBtn.addEventListener('click', runGradientDescent);
315
+ }
316
+
317
+ if (resetBtn) {
318
+ resetBtn.addEventListener('click', () => {
319
+ state.gdIterations = [];
320
+ drawGradientDescent();
321
+ });
322
+ }
323
+
324
+ drawGradientDescent();
325
+ }
326
+
327
+ function runGradientDescent() {
328
+ state.gdIterations = [];
329
+ let m = 0, c = 20; // Start with poor values
330
+ const alpha = state.learningRate;
331
+ const iterations = 50;
332
+
333
+ for (let i = 0; i < iterations; i++) {
334
+ let dm = 0, dc = 0;
335
+ const n = data.linearRegression.length;
336
+
337
+ // Calculate gradients
338
+ data.linearRegression.forEach(point => {
339
+ const predicted = m * point.experience + c;
340
+ const error = predicted - point.salary;
341
+ dm += (2 / n) * error * point.experience;
342
+ dc += (2 / n) * error;
343
+ });
344
+
345
+ // Update parameters
346
+ m -= alpha * dm;
347
+ c -= alpha * dc;
348
+
349
+ // Calculate loss
350
+ let loss = 0;
351
+ data.linearRegression.forEach(point => {
352
+ const predicted = m * point.experience + c;
353
+ const error = point.salary - predicted;
354
+ loss += error * error;
355
+ });
356
+ loss /= n;
357
+
358
+ state.gdIterations.push({ m, c, loss });
359
+ }
360
+
361
+ animateGradientDescent();
362
+ }
363
+
364
+ function animateGradientDescent() {
365
+ let step = 0;
366
+ const interval = setInterval(() => {
367
+ if (step >= state.gdIterations.length) {
368
+ clearInterval(interval);
369
+ return;
370
+ }
371
+
372
+ const iteration = state.gdIterations[step];
373
+ state.slope = iteration.m;
374
+ state.intercept = iteration.c;
375
+
376
+ // Update linear regression chart
377
+ drawLinearRegression();
378
+ drawGradientDescent(step);
379
+
380
+ step++;
381
+ }, 50);
382
+ }
383
+
384
+ function drawGradientDescent(currentStep = -1) {
385
+ const canvas = document.getElementById('gd-canvas');
386
+ if (!canvas) return;
387
+
388
+ const ctx = canvas.getContext('2d');
389
+ const width = canvas.width = canvas.offsetWidth;
390
+ const height = canvas.height = 400;
391
+
392
+ ctx.clearRect(0, 0, width, height);
393
+
394
+ if (state.gdIterations.length === 0) {
395
+ ctx.fillStyle = '#a9b4c2';
396
+ ctx.font = '16px sans-serif';
397
+ ctx.textAlign = 'center';
398
+ ctx.fillText('Click "Run Gradient Descent" to see the algorithm in action', width / 2, height / 2);
399
+ return;
400
+ }
401
+
402
+ const padding = 60;
403
+ const chartWidth = width - 2 * padding;
404
+ const chartHeight = height - 2 * padding;
405
+
406
+ const maxLoss = Math.max(...state.gdIterations.map(i => i.loss));
407
+ const minLoss = Math.min(...state.gdIterations.map(i => i.loss));
408
+
409
+ // Draw axes
410
+ ctx.strokeStyle = '#2a3544';
411
+ ctx.lineWidth = 2;
412
+ ctx.beginPath();
413
+ ctx.moveTo(padding, padding);
414
+ ctx.lineTo(padding, height - padding);
415
+ ctx.lineTo(width - padding, height - padding);
416
+ ctx.stroke();
417
+
418
+ const scaleX = (i) => padding + (i / (state.gdIterations.length - 1)) * chartWidth;
419
+ const scaleY = (loss) => height - padding - ((loss - minLoss) / (maxLoss - minLoss)) * chartHeight;
420
+
421
+ // Draw loss curve
422
+ ctx.strokeStyle = '#7ef0d4';
423
+ ctx.lineWidth = 2;
424
+ ctx.beginPath();
425
+ state.gdIterations.forEach((iter, i) => {
426
+ const x = scaleX(i);
427
+ const y = scaleY(iter.loss);
428
+ if (i === 0) {
429
+ ctx.moveTo(x, y);
430
+ } else {
431
+ ctx.lineTo(x, y);
432
+ }
433
+ });
434
+ ctx.stroke();
435
+
436
+ // Highlight current step
437
+ if (currentStep >= 0 && currentStep < state.gdIterations.length) {
438
+ const iter = state.gdIterations[currentStep];
439
+ const x = scaleX(currentStep);
440
+ const y = scaleY(iter.loss);
441
+
442
+ ctx.fillStyle = '#ff8c6a';
443
+ ctx.beginPath();
444
+ ctx.arc(x, y, 6, 0, 2 * Math.PI);
445
+ ctx.fill();
446
+
447
+ // Display current values
448
+ ctx.fillStyle = '#e8eef6';
449
+ ctx.font = '12px sans-serif';
450
+ ctx.textAlign = 'left';
451
+ ctx.fillText(`Step: ${currentStep + 1}`, padding + 10, padding + 20);
452
+ ctx.fillText(`Loss: ${iter.loss.toFixed(2)}`, padding + 10, padding + 40);
453
+ }
454
+
455
+ // Labels
456
+ ctx.fillStyle = '#a9b4c2';
457
+ ctx.font = '12px sans-serif';
458
+ ctx.textAlign = 'center';
459
+ ctx.fillText('Iterations', width / 2, height - 20);
460
+ ctx.save();
461
+ ctx.translate(20, height / 2);
462
+ ctx.rotate(-Math.PI / 2);
463
+ ctx.fillText('Loss (MSE)', 0, 0);
464
+ ctx.restore();
465
+ }
466
+
467
+ // Initialize everything when DOM is ready
468
+ function init() {
469
+ initSections();
470
+ initTOCLinks();
471
+
472
+ // Initialize first section visualizations
473
+ setTimeout(() => {
474
+ initLinearRegression();
475
+ }, 100);
476
+ }
477
+
478
+ if (document.readyState === 'loading') {
479
+ document.addEventListener('DOMContentLoaded', init);
480
+ } else {
481
+ init();
482
+ }
483
+
484
+ // SVM Visualizations
485
+ function initSVM() {
486
+ initSVMBasic();
487
+ initSVMMargin();
488
+ initSVMCParameter();
489
+ initSVMTraining();
490
+ initSVMKernel();
491
+ }
492
+
493
+ function initSVMBasic() {
494
+ const canvas = document.getElementById('svm-basic-canvas');
495
+ if (!canvas || canvas.dataset.initialized) return;
496
+ canvas.dataset.initialized = 'true';
497
+
498
+ const w1Slider = document.getElementById('svm-w1-slider');
499
+ const w2Slider = document.getElementById('svm-w2-slider');
500
+ const bSlider = document.getElementById('svm-b-slider');
501
+
502
+ if (w1Slider) {
503
+ w1Slider.addEventListener('input', (e) => {
504
+ state.svm.w1 = parseFloat(e.target.value);
505
+ document.getElementById('svm-w1-val').textContent = state.svm.w1.toFixed(1);
506
+ drawSVMBasic();
507
+ });
508
+ }
509
+
510
+ if (w2Slider) {
511
+ w2Slider.addEventListener('input', (e) => {
512
+ state.svm.w2 = parseFloat(e.target.value);
513
+ document.getElementById('svm-w2-val').textContent = state.svm.w2.toFixed(1);
514
+ drawSVMBasic();
515
+ });
516
+ }
517
+
518
+ if (bSlider) {
519
+ bSlider.addEventListener('input', (e) => {
520
+ state.svm.b = parseFloat(e.target.value);
521
+ document.getElementById('svm-b-val').textContent = state.svm.b.toFixed(1);
522
+ drawSVMBasic();
523
+ });
524
+ }
525
+
526
+ drawSVMBasic();
527
+ }
528
+
529
+ function drawSVMBasic() {
530
+ const canvas = document.getElementById('svm-basic-canvas');
531
+ if (!canvas) return;
532
+
533
+ const ctx = canvas.getContext('2d');
534
+ const width = canvas.width = canvas.offsetWidth;
535
+ const height = canvas.height = 450;
536
+
537
+ ctx.clearRect(0, 0, width, height);
538
+ ctx.fillStyle = '#1a2332';
539
+ ctx.fillRect(0, 0, width, height);
540
+
541
+ const padding = 60;
542
+ const chartWidth = width - 2 * padding;
543
+ const chartHeight = height - 2 * padding;
544
+
545
+ const xMin = 0, xMax = 10;
546
+ const yMin = 0, yMax = 10;
547
+
548
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
549
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
550
+
551
+ // Draw grid
552
+ ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)';
553
+ ctx.lineWidth = 1;
554
+ for (let i = 0; i <= 10; i++) {
555
+ const x = scaleX(i);
556
+ const y = scaleY(i);
557
+
558
+ ctx.beginPath();
559
+ ctx.moveTo(x, padding);
560
+ ctx.lineTo(x, height - padding);
561
+ ctx.stroke();
562
+
563
+ ctx.beginPath();
564
+ ctx.moveTo(padding, y);
565
+ ctx.lineTo(width - padding, y);
566
+ ctx.stroke();
567
+ }
568
+
569
+ // Draw axes
570
+ ctx.strokeStyle = '#2a3544';
571
+ ctx.lineWidth = 2;
572
+ ctx.beginPath();
573
+ ctx.moveTo(padding, padding);
574
+ ctx.lineTo(padding, height - padding);
575
+ ctx.lineTo(width - padding, height - padding);
576
+ ctx.stroke();
577
+
578
+ // Draw decision boundary
579
+ const w1 = state.svm.w1;
580
+ const w2 = state.svm.w2;
581
+ const b = state.svm.b;
582
+
583
+ if (Math.abs(w2) > 0.01) {
584
+ ctx.strokeStyle = '#6aa9ff';
585
+ ctx.lineWidth = 3;
586
+ ctx.beginPath();
587
+
588
+ const x1 = xMin;
589
+ const y1 = -(w1 * x1 + b) / w2;
590
+ const x2 = xMax;
591
+ const y2 = -(w1 * x2 + b) / w2;
592
+
593
+ ctx.moveTo(scaleX(x1), scaleY(y1));
594
+ ctx.lineTo(scaleX(x2), scaleY(y2));
595
+ ctx.stroke();
596
+ }
597
+
598
+ // Draw data points
599
+ data.svm.forEach(point => {
600
+ const x = scaleX(point.x1);
601
+ const y = scaleY(point.x2);
602
+ const score = w1 * point.x1 + w2 * point.x2 + b;
603
+
604
+ ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a';
605
+ ctx.beginPath();
606
+ ctx.arc(x, y, 8, 0, 2 * Math.PI);
607
+ ctx.fill();
608
+
609
+ ctx.strokeStyle = '#1a2332';
610
+ ctx.lineWidth = 2;
611
+ ctx.stroke();
612
+
613
+ // Label
614
+ ctx.fillStyle = '#e8eef6';
615
+ ctx.font = 'bold 12px sans-serif';
616
+ ctx.textAlign = 'center';
617
+ ctx.fillText(point.label, x, y - 15);
618
+
619
+ // Score
620
+ ctx.font = '11px monospace';
621
+ ctx.fillStyle = '#a9b4c2';
622
+ ctx.fillText(score.toFixed(2), x, y + 20);
623
+ });
624
+
625
+ // Labels
626
+ ctx.fillStyle = '#a9b4c2';
627
+ ctx.font = '13px sans-serif';
628
+ ctx.textAlign = 'center';
629
+ ctx.fillText('X₁', width / 2, height - 20);
630
+ ctx.save();
631
+ ctx.translate(20, height / 2);
632
+ ctx.rotate(-Math.PI / 2);
633
+ ctx.fillText('X₂', 0, 0);
634
+ ctx.restore();
635
+
636
+ // Equation
637
+ ctx.fillStyle = '#7ef0d4';
638
+ ctx.font = '14px monospace';
639
+ ctx.textAlign = 'left';
640
+ ctx.fillText(`w·x + b = ${w1.toFixed(1)}x₁ + ${w2.toFixed(1)}x₂ + ${b.toFixed(1)}`, padding + 10, padding + 25);
641
+ }
642
+
643
+ function initSVMMargin() {
644
+ const canvas = document.getElementById('svm-margin-canvas');
645
+ if (!canvas || canvas.dataset.initialized) return;
646
+ canvas.dataset.initialized = 'true';
647
+ drawSVMMargin();
648
+ }
649
+
650
+ function drawSVMMargin() {
651
+ const canvas = document.getElementById('svm-margin-canvas');
652
+ if (!canvas) return;
653
+
654
+ const ctx = canvas.getContext('2d');
655
+ const width = canvas.width = canvas.offsetWidth;
656
+ const height = canvas.height = 450;
657
+
658
+ ctx.clearRect(0, 0, width, height);
659
+ ctx.fillStyle = '#1a2332';
660
+ ctx.fillRect(0, 0, width, height);
661
+
662
+ const padding = 60;
663
+ const chartWidth = width - 2 * padding;
664
+ const chartHeight = height - 2 * padding;
665
+
666
+ const xMin = 0, xMax = 10;
667
+ const yMin = 0, yMax = 10;
668
+
669
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
670
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
671
+
672
+ // Use good values for visualization
673
+ const w1 = 0.5, w2 = -1, b = 5.5;
674
+
675
+ // Draw margin lines
676
+ if (Math.abs(w2) > 0.01) {
677
+ // Positive margin line
678
+ ctx.strokeStyle = '#ff8c6a';
679
+ ctx.lineWidth = 2;
680
+ ctx.setLineDash([5, 5]);
681
+ ctx.beginPath();
682
+ let x1 = xMin, y1 = -(w1 * x1 + b - 1) / w2;
683
+ let x2 = xMax, y2 = -(w1 * x2 + b - 1) / w2;
684
+ ctx.moveTo(scaleX(x1), scaleY(y1));
685
+ ctx.lineTo(scaleX(x2), scaleY(y2));
686
+ ctx.stroke();
687
+
688
+ // Negative margin line
689
+ ctx.beginPath();
690
+ y1 = -(w1 * x1 + b + 1) / w2;
691
+ y2 = -(w1 * x2 + b + 1) / w2;
692
+ ctx.moveTo(scaleX(x1), scaleY(y1));
693
+ ctx.lineTo(scaleX(x2), scaleY(y2));
694
+ ctx.stroke();
695
+
696
+ // Decision boundary
697
+ ctx.setLineDash([]);
698
+ ctx.strokeStyle = '#6aa9ff';
699
+ ctx.lineWidth = 3;
700
+ ctx.beginPath();
701
+ y1 = -(w1 * x1 + b) / w2;
702
+ y2 = -(w1 * x2 + b) / w2;
703
+ ctx.moveTo(scaleX(x1), scaleY(y1));
704
+ ctx.lineTo(scaleX(x2), scaleY(y2));
705
+ ctx.stroke();
706
+ }
707
+
708
+ // Draw data points
709
+ data.svm.forEach(point => {
710
+ const x = scaleX(point.x1);
711
+ const y = scaleY(point.x2);
712
+ const score = w1 * point.x1 + w2 * point.x2 + b;
713
+ const isSupport = Math.abs(Math.abs(score) - 1) < 0.5;
714
+
715
+ ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a';
716
+ ctx.beginPath();
717
+ ctx.arc(x, y, 8, 0, 2 * Math.PI);
718
+ ctx.fill();
719
+
720
+ // Highlight support vectors
721
+ if (isSupport) {
722
+ ctx.strokeStyle = '#7ef0d4';
723
+ ctx.lineWidth = 3;
724
+ ctx.beginPath();
725
+ ctx.arc(x, y, 14, 0, 2 * Math.PI);
726
+ ctx.stroke();
727
+ }
728
+
729
+ ctx.fillStyle = '#e8eef6';
730
+ ctx.font = 'bold 12px sans-serif';
731
+ ctx.textAlign = 'center';
732
+ ctx.fillText(point.label, x, y - 20);
733
+ });
734
+
735
+ // Show margin width
736
+ const wNorm = Math.sqrt(w1 * w1 + w2 * w2);
737
+ const marginWidth = 2 / wNorm;
738
+
739
+ ctx.fillStyle = '#7ef0d4';
740
+ ctx.font = '16px sans-serif';
741
+ ctx.textAlign = 'left';
742
+ ctx.fillText(`Margin Width: ${marginWidth.toFixed(2)}`, padding + 10, padding + 25);
743
+ ctx.fillText('Support vectors highlighted with cyan ring', padding + 10, padding + 50);
744
+ }
745
+
746
+ function initSVMCParameter() {
747
+ const canvas = document.getElementById('svm-c-canvas');
748
+ if (!canvas || canvas.dataset.initialized) return;
749
+ canvas.dataset.initialized = 'true';
750
+
751
+ const cSlider = document.getElementById('svm-c-slider');
752
+ if (cSlider) {
753
+ cSlider.addEventListener('input', (e) => {
754
+ const val = parseFloat(e.target.value);
755
+ state.svm.C = Math.pow(10, val);
756
+ document.getElementById('svm-c-val').textContent = state.svm.C.toFixed(state.svm.C < 10 ? 1 : 0);
757
+ drawSVMCParameter();
758
+ });
759
+ }
760
+
761
+ drawSVMCParameter();
762
+ }
763
+
764
+ function drawSVMCParameter() {
765
+ const canvas = document.getElementById('svm-c-canvas');
766
+ if (!canvas) return;
767
+
768
+ const ctx = canvas.getContext('2d');
769
+ const width = canvas.width = canvas.offsetWidth;
770
+ const height = canvas.height = 450;
771
+
772
+ ctx.clearRect(0, 0, width, height);
773
+ ctx.fillStyle = '#1a2332';
774
+ ctx.fillRect(0, 0, width, height);
775
+
776
+ const padding = 60;
777
+ const chartWidth = width - 2 * padding;
778
+ const chartHeight = height - 2 * padding;
779
+
780
+ const xMin = 0, xMax = 10;
781
+ const yMin = 0, yMax = 10;
782
+
783
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
784
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
785
+
786
+ // Adjust margin based on C
787
+ const C = state.svm.C;
788
+ const marginFactor = Math.min(1, 10 / C);
789
+ const w1 = 0.5 * marginFactor, w2 = -1 * marginFactor, b = 5.5;
790
+
791
+ // Calculate violations
792
+ let violations = 0;
793
+ data.svm.forEach(point => {
794
+ const score = w1 * point.x1 + w2 * point.x2 + b;
795
+ if (point.class * score < 1) violations++;
796
+ });
797
+
798
+ // Draw margin lines
799
+ if (Math.abs(w2) > 0.01) {
800
+ ctx.strokeStyle = '#ff8c6a';
801
+ ctx.lineWidth = 2;
802
+ ctx.setLineDash([5, 5]);
803
+ ctx.beginPath();
804
+ let x1 = xMin, y1 = -(w1 * x1 + b - 1) / w2;
805
+ let x2 = xMax, y2 = -(w1 * x2 + b - 1) / w2;
806
+ ctx.moveTo(scaleX(x1), scaleY(y1));
807
+ ctx.lineTo(scaleX(x2), scaleY(y2));
808
+ ctx.stroke();
809
+
810
+ ctx.beginPath();
811
+ y1 = -(w1 * x1 + b + 1) / w2;
812
+ y2 = -(w1 * x2 + b + 1) / w2;
813
+ ctx.moveTo(scaleX(x1), scaleY(y1));
814
+ ctx.lineTo(scaleX(x2), scaleY(y2));
815
+ ctx.stroke();
816
+
817
+ ctx.setLineDash([]);
818
+ ctx.strokeStyle = '#6aa9ff';
819
+ ctx.lineWidth = 3;
820
+ ctx.beginPath();
821
+ y1 = -(w1 * x1 + b) / w2;
822
+ y2 = -(w1 * x2 + b) / w2;
823
+ ctx.moveTo(scaleX(x1), scaleY(y1));
824
+ ctx.lineTo(scaleX(x2), scaleY(y2));
825
+ ctx.stroke();
826
+ }
827
+
828
+ // Draw points
829
+ data.svm.forEach(point => {
830
+ const x = scaleX(point.x1);
831
+ const y = scaleY(point.x2);
832
+ const score = w1 * point.x1 + w2 * point.x2 + b;
833
+ const violates = point.class * score < 1;
834
+
835
+ ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a';
836
+ ctx.beginPath();
837
+ ctx.arc(x, y, 8, 0, 2 * Math.PI);
838
+ ctx.fill();
839
+
840
+ if (violates) {
841
+ ctx.strokeStyle = '#ff4444';
842
+ ctx.lineWidth = 3;
843
+ ctx.stroke();
844
+ }
845
+ });
846
+
847
+ // Update info
848
+ const wNorm = Math.sqrt(w1 * w1 + w2 * w2);
849
+ const marginWidth = 2 / wNorm;
850
+ document.getElementById('margin-width').textContent = marginWidth.toFixed(2);
851
+ document.getElementById('violations-count').textContent = violations;
852
+ }
853
+
854
+ function initSVMTraining() {
855
+ const canvas = document.getElementById('svm-train-canvas');
856
+ if (!canvas || canvas.dataset.initialized) return;
857
+ canvas.dataset.initialized = 'true';
858
+
859
+ const trainBtn = document.getElementById('svm-train-btn');
860
+ const stepBtn = document.getElementById('svm-step-btn');
861
+ const resetBtn = document.getElementById('svm-reset-btn');
862
+
863
+ if (trainBtn) {
864
+ trainBtn.addEventListener('click', () => {
865
+ state.svm.training.step = 0;
866
+ state.svm.training.w = [0, 0];
867
+ state.svm.training.b = 0;
868
+ state.svm.training.isTraining = true;
869
+ autoTrain();
870
+ });
871
+ }
872
+
873
+ if (stepBtn) {
874
+ stepBtn.addEventListener('click', () => {
875
+ if (state.svm.training.step < data.svm.length) {
876
+ trainStep();
877
+ }
878
+ });
879
+ }
880
+
881
+ if (resetBtn) {
882
+ resetBtn.addEventListener('click', () => {
883
+ state.svm.training.step = 0;
884
+ state.svm.training.w = [0, 0];
885
+ state.svm.training.b = 0;
886
+ state.svm.training.isTraining = false;
887
+ updateTrainingInfo();
888
+ drawSVMTraining();
889
+ });
890
+ }
891
+
892
+ drawSVMTraining();
893
+ }
894
+
895
+ function trainStep() {
896
+ if (state.svm.training.step >= data.svm.length) return;
897
+
898
+ const point = data.svm[state.svm.training.step];
899
+ const w = state.svm.training.w;
900
+ const b = state.svm.training.b;
901
+ const lr = state.svm.training.learningRate;
902
+ const C = 1;
903
+
904
+ const score = w[0] * point.x1 + w[1] * point.x2 + b;
905
+ const violation = point.class * score < 1;
906
+
907
+ if (violation) {
908
+ w[0] = w[0] - lr * (w[0] - C * point.class * point.x1);
909
+ w[1] = w[1] - lr * (w[1] - C * point.class * point.x2);
910
+ state.svm.training.b = b + lr * C * point.class;
911
+ } else {
912
+ w[0] = w[0] - lr * w[0];
913
+ w[1] = w[1] - lr * w[1];
914
+ }
915
+
916
+ state.svm.training.step++;
917
+ updateTrainingInfo(point, violation);
918
+ drawSVMTraining();
919
+ }
920
+
921
+ function autoTrain() {
922
+ if (!state.svm.training.isTraining) return;
923
+
924
+ if (state.svm.training.step < data.svm.length) {
925
+ trainStep();
926
+ setTimeout(autoTrain, 800);
927
+ } else {
928
+ state.svm.training.isTraining = false;
929
+ }
930
+ }
931
+
932
+ function updateTrainingInfo(point = null, violation = null) {
933
+ document.getElementById('train-step').textContent = state.svm.training.step;
934
+ document.getElementById('train-point').textContent = point ? `${point.label} (${point.x1}, ${point.x2})` : '-';
935
+ document.getElementById('train-w').textContent = `${state.svm.training.w[0].toFixed(2)}, ${state.svm.training.w[1].toFixed(2)}`;
936
+ document.getElementById('train-b').textContent = state.svm.training.b.toFixed(2);
937
+ document.getElementById('train-violation').textContent = violation === null ? '-' : (violation ? 'YES ❌' : 'NO ✓');
938
+ }
939
+
940
+ function drawSVMTraining() {
941
+ const canvas = document.getElementById('svm-train-canvas');
942
+ if (!canvas) return;
943
+
944
+ const ctx = canvas.getContext('2d');
945
+ const width = canvas.width = canvas.offsetWidth;
946
+ const height = canvas.height = 450;
947
+
948
+ ctx.clearRect(0, 0, width, height);
949
+ ctx.fillStyle = '#1a2332';
950
+ ctx.fillRect(0, 0, width, height);
951
+
952
+ const padding = 60;
953
+ const chartWidth = width - 2 * padding;
954
+ const chartHeight = height - 2 * padding;
955
+
956
+ const xMin = 0, xMax = 10;
957
+ const yMin = 0, yMax = 10;
958
+
959
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
960
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
961
+
962
+ const w = state.svm.training.w;
963
+ const b = state.svm.training.b;
964
+
965
+ // Draw boundary if weights are non-zero
966
+ if (Math.abs(w[1]) > 0.01) {
967
+ ctx.strokeStyle = '#6aa9ff';
968
+ ctx.lineWidth = 3;
969
+ ctx.beginPath();
970
+ const x1 = xMin, y1 = -(w[0] * x1 + b) / w[1];
971
+ const x2 = xMax, y2 = -(w[0] * x2 + b) / w[1];
972
+ ctx.moveTo(scaleX(x1), scaleY(y1));
973
+ ctx.lineTo(scaleX(x2), scaleY(y2));
974
+ ctx.stroke();
975
+ }
976
+
977
+ // Draw points
978
+ data.svm.forEach((point, i) => {
979
+ const x = scaleX(point.x1);
980
+ const y = scaleY(point.x2);
981
+ const processed = i < state.svm.training.step;
982
+ const current = i === state.svm.training.step - 1;
983
+
984
+ ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a';
985
+ ctx.globalAlpha = processed ? 1 : 0.3;
986
+ ctx.beginPath();
987
+ ctx.arc(x, y, 8, 0, 2 * Math.PI);
988
+ ctx.fill();
989
+
990
+ if (current) {
991
+ ctx.globalAlpha = 1;
992
+ ctx.strokeStyle = '#ffff00';
993
+ ctx.lineWidth = 3;
994
+ ctx.beginPath();
995
+ ctx.arc(x, y, 14, 0, 2 * Math.PI);
996
+ ctx.stroke();
997
+ }
998
+
999
+ ctx.globalAlpha = 1;
1000
+ ctx.fillStyle = '#e8eef6';
1001
+ ctx.font = 'bold 12px sans-serif';
1002
+ ctx.textAlign = 'center';
1003
+ ctx.fillText(point.label, x, y - 15);
1004
+ });
1005
+ }
1006
+
1007
+ function initSVMKernel() {
1008
+ const canvas = document.getElementById('svm-kernel-canvas');
1009
+ if (!canvas || canvas.dataset.initialized) return;
1010
+ canvas.dataset.initialized = 'true';
1011
+
1012
+ const kernelRadios = document.querySelectorAll('input[name="kernel"]');
1013
+ kernelRadios.forEach(radio => {
1014
+ radio.addEventListener('change', (e) => {
1015
+ state.svm.kernel = e.target.value;
1016
+ const paramGroup = document.getElementById('kernel-param-group');
1017
+ if (paramGroup) {
1018
+ paramGroup.style.display = state.svm.kernel === 'linear' ? 'none' : 'block';
1019
+ }
1020
+ drawSVMKernel();
1021
+ });
1022
+ });
1023
+
1024
+ const paramSlider = document.getElementById('kernel-param-slider');
1025
+ if (paramSlider) {
1026
+ paramSlider.addEventListener('input', (e) => {
1027
+ state.svm.kernelParam = parseFloat(e.target.value);
1028
+ document.getElementById('kernel-param-val').textContent = state.svm.kernelParam.toFixed(1);
1029
+ drawSVMKernel();
1030
+ });
1031
+ }
1032
+
1033
+ drawSVMKernel();
1034
+ }
1035
+
1036
+ function drawSVMKernel() {
1037
+ const canvas = document.getElementById('svm-kernel-canvas');
1038
+ if (!canvas) return;
1039
+
1040
+ const ctx = canvas.getContext('2d');
1041
+ const width = canvas.width = canvas.offsetWidth;
1042
+ const height = canvas.height = 500;
1043
+
1044
+ ctx.clearRect(0, 0, width, height);
1045
+ ctx.fillStyle = '#1a2332';
1046
+ ctx.fillRect(0, 0, width, height);
1047
+
1048
+ const padding = 60;
1049
+ const chartWidth = width - 2 * padding;
1050
+ const chartHeight = height - 2 * padding;
1051
+
1052
+ // Generate circular data
1053
+ const innerPoints = [];
1054
+ const outerPoints = [];
1055
+
1056
+ for (let i = 0; i < 15; i++) {
1057
+ const angle = (i / 15) * 2 * Math.PI;
1058
+ innerPoints.push({ x: 5 + 1.5 * Math.cos(angle), y: 5 + 1.5 * Math.sin(angle), class: 1 });
1059
+ }
1060
+
1061
+ for (let i = 0; i < 20; i++) {
1062
+ const angle = (i / 20) * 2 * Math.PI;
1063
+ const r = 3.5 + Math.random() * 0.5;
1064
+ outerPoints.push({ x: 5 + r * Math.cos(angle), y: 5 + r * Math.sin(angle), class: -1 });
1065
+ }
1066
+
1067
+ const allPoints = [...innerPoints, ...outerPoints];
1068
+
1069
+ const xMin = 0, xMax = 10;
1070
+ const yMin = 0, yMax = 10;
1071
+
1072
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
1073
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
1074
+
1075
+ // Draw decision boundary based on kernel
1076
+ if (state.svm.kernel === 'linear') {
1077
+ // Linear can't separate circular data well
1078
+ ctx.strokeStyle = 'rgba(106, 169, 255, 0.5)';
1079
+ ctx.lineWidth = 2;
1080
+ ctx.beginPath();
1081
+ ctx.moveTo(scaleX(2), scaleY(2));
1082
+ ctx.lineTo(scaleX(8), scaleY(8));
1083
+ ctx.stroke();
1084
+ } else if (state.svm.kernel === 'polynomial' || state.svm.kernel === 'rbf') {
1085
+ // Draw circular boundary
1086
+ ctx.strokeStyle = '#6aa9ff';
1087
+ ctx.lineWidth = 3;
1088
+ ctx.beginPath();
1089
+ const radius = state.svm.kernel === 'polynomial' ? 2.5 : 2.3 + state.svm.kernelParam * 0.1;
1090
+ ctx.arc(scaleX(5), scaleY(5), radius * (chartWidth / 10), 0, 2 * Math.PI);
1091
+ ctx.stroke();
1092
+ }
1093
+
1094
+ // Draw points
1095
+ allPoints.forEach(point => {
1096
+ const x = scaleX(point.x);
1097
+ const y = scaleY(point.y);
1098
+
1099
+ ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a';
1100
+ ctx.beginPath();
1101
+ ctx.arc(x, y, 5, 0, 2 * Math.PI);
1102
+ ctx.fill();
1103
+ });
1104
+
1105
+ // Draw kernel info
1106
+ ctx.fillStyle = '#7ef0d4';
1107
+ ctx.font = '16px sans-serif';
1108
+ ctx.textAlign = 'left';
1109
+ const kernelName = state.svm.kernel === 'linear' ? 'Linear Kernel' :
1110
+ state.svm.kernel === 'polynomial' ? 'Polynomial Kernel' : 'RBF Kernel';
1111
+ ctx.fillText(kernelName, padding + 10, padding + 25);
1112
+
1113
+ if (state.svm.kernel === 'linear') {
1114
+ ctx.font = '13px sans-serif';
1115
+ ctx.fillStyle = '#ff8c6a';
1116
+ ctx.fillText('❌ Linear kernel cannot separate circular data!', padding + 10, padding + 50);
1117
+ } else {
1118
+ ctx.font = '13px sans-serif';
1119
+ ctx.fillStyle = '#7ef0d4';
1120
+ ctx.fillText('✓ Non-linear kernel successfully separates the data', padding + 10, padding + 50);
1121
+ }
1122
+ }
1123
+
1124
+ // Logistic Regression Visualizations
1125
+ function initLogistic() {
1126
+ initSigmoid();
1127
+ initLogisticClassification();
1128
+ }
1129
+
1130
+ function initSigmoid() {
1131
+ const canvas = document.getElementById('sigmoid-canvas');
1132
+ if (!canvas || canvas.dataset.initialized) return;
1133
+ canvas.dataset.initialized = 'true';
1134
+ drawSigmoid();
1135
+ }
1136
+
1137
+ function drawSigmoid() {
1138
+ const canvas = document.getElementById('sigmoid-canvas');
1139
+ if (!canvas) return;
1140
+
1141
+ const ctx = canvas.getContext('2d');
1142
+ const width = canvas.width = canvas.offsetWidth;
1143
+ const height = canvas.height = 350;
1144
+
1145
+ ctx.clearRect(0, 0, width, height);
1146
+ ctx.fillStyle = '#1a2332';
1147
+ ctx.fillRect(0, 0, width, height);
1148
+
1149
+ const padding = 60;
1150
+ const chartWidth = width - 2 * padding;
1151
+ const chartHeight = height - 2 * padding;
1152
+
1153
+ const zMin = -10, zMax = 10;
1154
+ const scaleX = (z) => padding + ((z - zMin) / (zMax - zMin)) * chartWidth;
1155
+ const scaleY = (sig) => height - padding - sig * chartHeight;
1156
+
1157
+ // Draw grid
1158
+ ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)';
1159
+ ctx.lineWidth = 1;
1160
+ for (let i = 0; i <= 10; i++) {
1161
+ const x = padding + (chartWidth / 10) * i;
1162
+ ctx.beginPath();
1163
+ ctx.moveTo(x, padding);
1164
+ ctx.lineTo(x, height - padding);
1165
+ ctx.stroke();
1166
+
1167
+ const y = padding + (chartHeight / 10) * i;
1168
+ ctx.beginPath();
1169
+ ctx.moveTo(padding, y);
1170
+ ctx.lineTo(width - padding, y);
1171
+ ctx.stroke();
1172
+ }
1173
+
1174
+ // Draw axes
1175
+ ctx.strokeStyle = '#2a3544';
1176
+ ctx.lineWidth = 2;
1177
+ ctx.beginPath();
1178
+ ctx.moveTo(padding, padding);
1179
+ ctx.lineTo(padding, height - padding);
1180
+ ctx.lineTo(width - padding, height - padding);
1181
+ ctx.stroke();
1182
+
1183
+ // Draw threshold line at 0.5
1184
+ ctx.strokeStyle = '#ff8c6a';
1185
+ ctx.lineWidth = 1;
1186
+ ctx.setLineDash([5, 5]);
1187
+ ctx.beginPath();
1188
+ ctx.moveTo(padding, scaleY(0.5));
1189
+ ctx.lineTo(width - padding, scaleY(0.5));
1190
+ ctx.stroke();
1191
+ ctx.setLineDash([]);
1192
+
1193
+ // Draw sigmoid curve
1194
+ ctx.strokeStyle = '#7ef0d4';
1195
+ ctx.lineWidth = 3;
1196
+ ctx.beginPath();
1197
+ for (let z = zMin; z <= zMax; z += 0.1) {
1198
+ const sig = 1 / (1 + Math.exp(-z));
1199
+ const x = scaleX(z);
1200
+ const y = scaleY(sig);
1201
+ if (z === zMin) ctx.moveTo(x, y);
1202
+ else ctx.lineTo(x, y);
1203
+ }
1204
+ ctx.stroke();
1205
+
1206
+ // Labels
1207
+ ctx.fillStyle = '#a9b4c2';
1208
+ ctx.font = '12px sans-serif';
1209
+ ctx.textAlign = 'center';
1210
+ ctx.fillText('z (input)', width / 2, height - 20);
1211
+ ctx.save();
1212
+ ctx.translate(20, height / 2);
1213
+ ctx.rotate(-Math.PI / 2);
1214
+ ctx.fillText('σ(z) probability', 0, 0);
1215
+ ctx.restore();
1216
+
1217
+ // Annotations
1218
+ ctx.fillStyle = '#7ef0d4';
1219
+ ctx.textAlign = 'left';
1220
+ ctx.fillText('σ(z) = 1/(1+e⁻ᶻ)', padding + 10, padding + 25);
1221
+ ctx.fillStyle = '#ff8c6a';
1222
+ ctx.fillText('Threshold = 0.5', padding + 10, scaleY(0.5) - 10);
1223
+ }
1224
+
1225
+ function initLogisticClassification() {
1226
+ const canvas = document.getElementById('logistic-canvas');
1227
+ if (!canvas || canvas.dataset.initialized) return;
1228
+ canvas.dataset.initialized = 'true';
1229
+ drawLogisticClassification();
1230
+ }
1231
+
1232
+ function drawLogisticClassification() {
1233
+ const canvas = document.getElementById('logistic-canvas');
1234
+ if (!canvas) return;
1235
+
1236
+ const ctx = canvas.getContext('2d');
1237
+ const width = canvas.width = canvas.offsetWidth;
1238
+ const height = canvas.height = 400;
1239
+
1240
+ ctx.clearRect(0, 0, width, height);
1241
+ ctx.fillStyle = '#1a2332';
1242
+ ctx.fillRect(0, 0, width, height);
1243
+
1244
+ const padding = 60;
1245
+ const chartWidth = width - 2 * padding;
1246
+ const chartHeight = height - 2 * padding;
1247
+
1248
+ const hMin = 140, hMax = 210;
1249
+ const scaleX = (h) => padding + ((h - hMin) / (hMax - hMin)) * chartWidth;
1250
+ const scaleY = (p) => height - padding - p * chartHeight;
1251
+
1252
+ // Draw sigmoid curve
1253
+ ctx.strokeStyle = '#6aa9ff';
1254
+ ctx.lineWidth = 3;
1255
+ ctx.beginPath();
1256
+ for (let h = hMin; h <= hMax; h += 1) {
1257
+ const z = (h - 170) / 10; // Simple linear transformation
1258
+ const p = 1 / (1 + Math.exp(-z));
1259
+ const x = scaleX(h);
1260
+ const y = scaleY(p);
1261
+ if (h === hMin) ctx.moveTo(x, y);
1262
+ else ctx.lineTo(x, y);
1263
+ }
1264
+ ctx.stroke();
1265
+
1266
+ // Draw threshold line
1267
+ ctx.strokeStyle = '#ff8c6a';
1268
+ ctx.setLineDash([5, 5]);
1269
+ ctx.beginPath();
1270
+ ctx.moveTo(padding, scaleY(0.5));
1271
+ ctx.lineTo(width - padding, scaleY(0.5));
1272
+ ctx.stroke();
1273
+ ctx.setLineDash([]);
1274
+
1275
+ // Draw data points
1276
+ data.logistic.forEach(point => {
1277
+ const x = scaleX(point.height);
1278
+ const y = scaleY(point.prob);
1279
+
1280
+ ctx.fillStyle = point.label === 1 ? '#7ef0d4' : '#ff8c6a';
1281
+ ctx.beginPath();
1282
+ ctx.arc(x, y, 6, 0, 2 * Math.PI);
1283
+ ctx.fill();
1284
+
1285
+ // Label
1286
+ ctx.fillStyle = '#e8eef6';
1287
+ ctx.font = '11px sans-serif';
1288
+ ctx.textAlign = 'center';
1289
+ ctx.fillText(point.height, x, height - padding + 20);
1290
+ });
1291
+
1292
+ // Labels
1293
+ ctx.fillStyle = '#a9b4c2';
1294
+ ctx.font = '12px sans-serif';
1295
+ ctx.textAlign = 'center';
1296
+ ctx.fillText('Height (cm)', width / 2, height - 20);
1297
+ ctx.save();
1298
+ ctx.translate(20, height / 2);
1299
+ ctx.rotate(-Math.PI / 2);
1300
+ ctx.fillText('P(Tall)', 0, 0);
1301
+ ctx.restore();
1302
+ }
1303
+
1304
+ // KNN Visualization
1305
+ let knnState = { testPoint: { x: 2.5, y: 2.5 }, k: 3, distanceMetric: 'euclidean', dragging: false };
1306
+
1307
+ function initKNN() {
1308
+ const canvas = document.getElementById('knn-canvas');
1309
+ if (!canvas || canvas.dataset.initialized) return;
1310
+ canvas.dataset.initialized = 'true';
1311
+
1312
+ const kSlider = document.getElementById('knn-k-slider');
1313
+ if (kSlider) {
1314
+ kSlider.addEventListener('input', (e) => {
1315
+ knnState.k = parseInt(e.target.value);
1316
+ document.getElementById('knn-k-val').textContent = knnState.k;
1317
+ drawKNN();
1318
+ });
1319
+ }
1320
+
1321
+ const distanceRadios = document.querySelectorAll('input[name="knn-distance"]');
1322
+ distanceRadios.forEach(radio => {
1323
+ radio.addEventListener('change', (e) => {
1324
+ knnState.distanceMetric = e.target.value;
1325
+ drawKNN();
1326
+ });
1327
+ });
1328
+
1329
+ canvas.addEventListener('mousedown', startDragKNN);
1330
+ canvas.addEventListener('mousemove', dragKNN);
1331
+ canvas.addEventListener('mouseup', stopDragKNN);
1332
+
1333
+ drawKNN();
1334
+ }
1335
+
1336
+ function startDragKNN(e) {
1337
+ const canvas = document.getElementById('knn-canvas');
1338
+ const rect = canvas.getBoundingClientRect();
1339
+ const mx = e.clientX - rect.left;
1340
+ const my = e.clientY - rect.top;
1341
+
1342
+ const padding = 60;
1343
+ const chartWidth = canvas.width - 2 * padding;
1344
+ const chartHeight = canvas.height - 2 * padding;
1345
+
1346
+ const tx = padding + (knnState.testPoint.x / 6) * chartWidth;
1347
+ const ty = canvas.height - padding - (knnState.testPoint.y / 6) * chartHeight;
1348
+
1349
+ if (Math.abs(mx - tx) < 15 && Math.abs(my - ty) < 15) {
1350
+ knnState.dragging = true;
1351
+ }
1352
+ }
1353
+
1354
+ function dragKNN(e) {
1355
+ if (!knnState.dragging) return;
1356
+
1357
+ const canvas = document.getElementById('knn-canvas');
1358
+ const rect = canvas.getBoundingClientRect();
1359
+ const mx = e.clientX - rect.left;
1360
+ const my = e.clientY - rect.top;
1361
+
1362
+ const padding = 60;
1363
+ const chartWidth = canvas.width - 2 * padding;
1364
+ const chartHeight = canvas.height - 2 * padding;
1365
+
1366
+ knnState.testPoint.x = Math.max(0, Math.min(6, ((mx - padding) / chartWidth) * 6));
1367
+ knnState.testPoint.y = Math.max(0, Math.min(6, ((canvas.height - padding - my) / chartHeight) * 6));
1368
+
1369
+ drawKNN();
1370
+ }
1371
+
1372
+ function stopDragKNN() {
1373
+ knnState.dragging = false;
1374
+ }
1375
+
1376
+ function drawKNN() {
1377
+ const canvas = document.getElementById('knn-canvas');
1378
+ if (!canvas) return;
1379
+
1380
+ const ctx = canvas.getContext('2d');
1381
+ const width = canvas.width = canvas.offsetWidth;
1382
+ const height = canvas.height = 450;
1383
+
1384
+ ctx.clearRect(0, 0, width, height);
1385
+ ctx.fillStyle = '#1a2332';
1386
+ ctx.fillRect(0, 0, width, height);
1387
+
1388
+ const padding = 60;
1389
+ const chartWidth = width - 2 * padding;
1390
+ const chartHeight = height - 2 * padding;
1391
+
1392
+ const scaleX = (x) => padding + (x / 6) * chartWidth;
1393
+ const scaleY = (y) => height - padding - (y / 6) * chartHeight;
1394
+
1395
+ // Calculate distances
1396
+ const distances = data.knn.map(point => {
1397
+ let d;
1398
+ if (knnState.distanceMetric === 'euclidean') {
1399
+ d = Math.sqrt(Math.pow(point.x - knnState.testPoint.x, 2) + Math.pow(point.y - knnState.testPoint.y, 2));
1400
+ } else {
1401
+ d = Math.abs(point.x - knnState.testPoint.x) + Math.abs(point.y - knnState.testPoint.y);
1402
+ }
1403
+ return { ...point, distance: d };
1404
+ });
1405
+
1406
+ distances.sort((a, b) => a.distance - b.distance);
1407
+ const kNearest = distances.slice(0, knnState.k);
1408
+
1409
+ // Count votes
1410
+ const votes = {};
1411
+ kNearest.forEach(p => {
1412
+ votes[p.class] = (votes[p.class] || 0) + 1;
1413
+ });
1414
+ const prediction = Object.keys(votes).reduce((a, b) => votes[a] > votes[b] ? a : b);
1415
+
1416
+ // Draw lines to K nearest
1417
+ kNearest.forEach(point => {
1418
+ ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)';
1419
+ ctx.lineWidth = 1;
1420
+ ctx.beginPath();
1421
+ ctx.moveTo(scaleX(knnState.testPoint.x), scaleY(knnState.testPoint.y));
1422
+ ctx.lineTo(scaleX(point.x), scaleY(point.y));
1423
+ ctx.stroke();
1424
+ });
1425
+
1426
+ // Draw training points
1427
+ distances.forEach(point => {
1428
+ const x = scaleX(point.x);
1429
+ const y = scaleY(point.y);
1430
+ const isNearest = kNearest.includes(point);
1431
+
1432
+ ctx.fillStyle = point.class === 'orange' ? '#ff8c6a' : '#ffeb3b';
1433
+ ctx.globalAlpha = isNearest ? 1 : 0.5;
1434
+ ctx.beginPath();
1435
+ ctx.arc(x, y, 8, 0, 2 * Math.PI);
1436
+ ctx.fill();
1437
+
1438
+ if (isNearest) {
1439
+ ctx.strokeStyle = '#7ef0d4';
1440
+ ctx.lineWidth = 2;
1441
+ ctx.globalAlpha = 1;
1442
+ ctx.beginPath();
1443
+ ctx.arc(x, y, 12, 0, 2 * Math.PI);
1444
+ ctx.stroke();
1445
+ }
1446
+ ctx.globalAlpha = 1;
1447
+ });
1448
+
1449
+ // Draw test point
1450
+ const tx = scaleX(knnState.testPoint.x);
1451
+ const ty = scaleY(knnState.testPoint.y);
1452
+ ctx.fillStyle = prediction === 'orange' ? '#ff8c6a' : '#ffeb3b';
1453
+ ctx.beginPath();
1454
+ ctx.arc(tx, ty, 12, 0, 2 * Math.PI);
1455
+ ctx.fill();
1456
+ ctx.strokeStyle = '#6aa9ff';
1457
+ ctx.lineWidth = 3;
1458
+ ctx.stroke();
1459
+
1460
+ // Info
1461
+ ctx.fillStyle = '#7ef0d4';
1462
+ ctx.font = '14px sans-serif';
1463
+ ctx.textAlign = 'left';
1464
+ ctx.fillText(`K=${knnState.k} | Prediction: ${prediction}`, padding + 10, padding + 25);
1465
+ ctx.fillText(`Votes: Orange=${votes.orange || 0}, Yellow=${votes.yellow || 0}`, padding + 10, padding + 50);
1466
+ }
1467
+
1468
+ // Model Evaluation
1469
+ function initModelEvaluation() {
1470
+ initConfusionMatrix();
1471
+ initROC();
1472
+ initR2();
1473
+ }
1474
+
1475
+ function initConfusionMatrix() {
1476
+ const canvas = document.getElementById('confusion-canvas');
1477
+ if (!canvas || canvas.dataset.initialized) return;
1478
+ canvas.dataset.initialized = 'true';
1479
+ drawConfusionMatrix();
1480
+ }
1481
+
1482
+ function drawConfusionMatrix() {
1483
+ const canvas = document.getElementById('confusion-canvas');
1484
+ if (!canvas) return;
1485
+
1486
+ const ctx = canvas.getContext('2d');
1487
+ const width = canvas.width = canvas.offsetWidth;
1488
+ const height = canvas.height = 300;
1489
+
1490
+ ctx.clearRect(0, 0, width, height);
1491
+ ctx.fillStyle = '#1a2332';
1492
+ ctx.fillRect(0, 0, width, height);
1493
+
1494
+ const size = Math.min(width, height) - 100;
1495
+ const cellSize = size / 2;
1496
+ const startX = (width - size) / 2;
1497
+ const startY = 50;
1498
+
1499
+ const cm = { tp: 600, fp: 100, fn: 300, tn: 900 };
1500
+
1501
+ // Draw cells
1502
+ const cells = [
1503
+ { x: startX, y: startY, val: cm.tp, label: 'TP', color: '#7ef0d4' },
1504
+ { x: startX + cellSize, y: startY, val: cm.fn, label: 'FN', color: '#ff8c6a' },
1505
+ { x: startX, y: startY + cellSize, val: cm.fp, label: 'FP', color: '#ff8c6a' },
1506
+ { x: startX + cellSize, y: startY + cellSize, val: cm.tn, label: 'TN', color: '#7ef0d4' }
1507
+ ];
1508
+
1509
+ cells.forEach(cell => {
1510
+ ctx.fillStyle = cell.color + '22';
1511
+ ctx.fillRect(cell.x, cell.y, cellSize, cellSize);
1512
+ ctx.strokeStyle = cell.color;
1513
+ ctx.lineWidth = 2;
1514
+ ctx.strokeRect(cell.x, cell.y, cellSize, cellSize);
1515
+
1516
+ ctx.fillStyle = cell.color;
1517
+ ctx.font = 'bold 16px sans-serif';
1518
+ ctx.textAlign = 'center';
1519
+ ctx.fillText(cell.label, cell.x + cellSize / 2, cell.y + cellSize / 2 - 10);
1520
+ ctx.font = 'bold 32px sans-serif';
1521
+ ctx.fillText(cell.val, cell.x + cellSize / 2, cell.y + cellSize / 2 + 25);
1522
+ });
1523
+
1524
+ // Labels
1525
+ ctx.fillStyle = '#a9b4c2';
1526
+ ctx.font = '14px sans-serif';
1527
+ ctx.textAlign = 'center';
1528
+ ctx.fillText('Predicted Positive', startX + cellSize / 2, startY - 15);
1529
+ ctx.fillText('Predicted Negative', startX + cellSize * 1.5, startY - 15);
1530
+ ctx.save();
1531
+ ctx.translate(startX - 30, startY + cellSize / 2);
1532
+ ctx.rotate(-Math.PI / 2);
1533
+ ctx.fillText('Actual Positive', 0, 0);
1534
+ ctx.restore();
1535
+ ctx.save();
1536
+ ctx.translate(startX - 30, startY + cellSize * 1.5);
1537
+ ctx.rotate(-Math.PI / 2);
1538
+ ctx.fillText('Actual Negative', 0, 0);
1539
+ ctx.restore();
1540
+ }
1541
+
1542
+ let rocState = { threshold: 0.5 };
1543
+
1544
+ function initROC() {
1545
+ const canvas = document.getElementById('roc-canvas');
1546
+ if (!canvas || canvas.dataset.initialized) return;
1547
+ canvas.dataset.initialized = 'true';
1548
+
1549
+ const slider = document.getElementById('roc-threshold-slider');
1550
+ if (slider) {
1551
+ slider.addEventListener('input', (e) => {
1552
+ rocState.threshold = parseFloat(e.target.value);
1553
+ document.getElementById('roc-threshold-val').textContent = rocState.threshold.toFixed(1);
1554
+ drawROC();
1555
+ });
1556
+ }
1557
+
1558
+ drawROC();
1559
+ }
1560
+
1561
+ function drawROC() {
1562
+ const canvas = document.getElementById('roc-canvas');
1563
+ if (!canvas) return;
1564
+
1565
+ const ctx = canvas.getContext('2d');
1566
+ const width = canvas.width = canvas.offsetWidth;
1567
+ const height = canvas.height = 450;
1568
+
1569
+ ctx.clearRect(0, 0, width, height);
1570
+ ctx.fillStyle = '#1a2332';
1571
+ ctx.fillRect(0, 0, width, height);
1572
+
1573
+ const padding = 60;
1574
+ const chartSize = Math.min(width - 2 * padding, height - 2 * padding);
1575
+ const chartX = (width - chartSize) / 2;
1576
+ const chartY = (height - chartSize) / 2;
1577
+
1578
+ // Calculate ROC points
1579
+ const rocPoints = [];
1580
+ for (let t = 0; t <= 1; t += 0.1) {
1581
+ let tp = 0, fp = 0, tn = 0, fn = 0;
1582
+ data.roc.forEach(e => {
1583
+ const pred = e.score >= t ? 1 : 0;
1584
+ if (e.true_label === 1 && pred === 1) tp++;
1585
+ else if (e.true_label === 0 && pred === 1) fp++;
1586
+ else if (e.true_label === 1 && pred === 0) fn++;
1587
+ else tn++;
1588
+ });
1589
+ const tpr = tp / (tp + fn) || 0;
1590
+ const fpr = fp / (fp + tn) || 0;
1591
+ rocPoints.push({ t, tpr, fpr });
1592
+ }
1593
+
1594
+ // Current threshold point
1595
+ let tp = 0, fp = 0, tn = 0, fn = 0;
1596
+ data.roc.forEach(e => {
1597
+ const pred = e.score >= rocState.threshold ? 1 : 0;
1598
+ if (e.true_label === 1 && pred === 1) tp++;
1599
+ else if (e.true_label === 0 && pred === 1) fp++;
1600
+ else if (e.true_label === 1 && pred === 0) fn++;
1601
+ else tn++;
1602
+ });
1603
+ const tpr = tp / (tp + fn) || 0;
1604
+ const fpr = fp / (fp + tn) || 0;
1605
+
1606
+ // Draw diagonal (random)
1607
+ ctx.strokeStyle = 'rgba(255, 140, 106, 0.5)';
1608
+ ctx.lineWidth = 2;
1609
+ ctx.setLineDash([5, 5]);
1610
+ ctx.beginPath();
1611
+ ctx.moveTo(chartX, chartY + chartSize);
1612
+ ctx.lineTo(chartX + chartSize, chartY);
1613
+ ctx.stroke();
1614
+ ctx.setLineDash([]);
1615
+
1616
+ // Draw ROC curve
1617
+ ctx.strokeStyle = '#6aa9ff';
1618
+ ctx.lineWidth = 3;
1619
+ ctx.beginPath();
1620
+ rocPoints.forEach((p, i) => {
1621
+ const x = chartX + p.fpr * chartSize;
1622
+ const y = chartY + chartSize - p.tpr * chartSize;
1623
+ if (i === 0) ctx.moveTo(x, y);
1624
+ else ctx.lineTo(x, y);
1625
+ });
1626
+ ctx.stroke();
1627
+
1628
+ // Draw current point
1629
+ const cx = chartX + fpr * chartSize;
1630
+ const cy = chartY + chartSize - tpr * chartSize;
1631
+ ctx.fillStyle = '#7ef0d4';
1632
+ ctx.beginPath();
1633
+ ctx.arc(cx, cy, 8, 0, 2 * Math.PI);
1634
+ ctx.fill();
1635
+
1636
+ // Draw axes
1637
+ ctx.strokeStyle = '#2a3544';
1638
+ ctx.lineWidth = 2;
1639
+ ctx.beginPath();
1640
+ ctx.rect(chartX, chartY, chartSize, chartSize);
1641
+ ctx.stroke();
1642
+
1643
+ // Labels
1644
+ ctx.fillStyle = '#a9b4c2';
1645
+ ctx.font = '12px sans-serif';
1646
+ ctx.textAlign = 'center';
1647
+ ctx.fillText('FPR (False Positive Rate)', width / 2, height - 20);
1648
+ ctx.save();
1649
+ ctx.translate(20, height / 2);
1650
+ ctx.rotate(-Math.PI / 2);
1651
+ ctx.fillText('TPR (True Positive Rate)', 0, 0);
1652
+ ctx.restore();
1653
+
1654
+ // Info
1655
+ ctx.fillStyle = '#7ef0d4';
1656
+ ctx.font = '14px sans-serif';
1657
+ ctx.textAlign = 'left';
1658
+ ctx.fillText(`TPR: ${tpr.toFixed(2)} | FPR: ${fpr.toFixed(2)}`, chartX + 10, chartY + 25);
1659
+ ctx.fillText(`TP=${tp} FP=${fp} TN=${tn} FN=${fn}`, chartX + 10, chartY + 50);
1660
+ }
1661
+
1662
+ function initR2() {
1663
+ const canvas = document.getElementById('r2-canvas');
1664
+ if (!canvas || canvas.dataset.initialized) return;
1665
+ canvas.dataset.initialized = 'true';
1666
+ drawR2();
1667
+ }
1668
+
1669
+ function drawR2() {
1670
+ const canvas = document.getElementById('r2-canvas');
1671
+ if (!canvas) return;
1672
+
1673
+ const ctx = canvas.getContext('2d');
1674
+ const width = canvas.width = canvas.offsetWidth;
1675
+ const height = canvas.height = 350;
1676
+
1677
+ ctx.clearRect(0, 0, width, height);
1678
+ ctx.fillStyle = '#1a2332';
1679
+ ctx.fillRect(0, 0, width, height);
1680
+
1681
+ // Dummy R² data
1682
+ const r2data = [
1683
+ { x: 150, y: 50, pred: 52 },
1684
+ { x: 160, y: 60, pred: 61 },
1685
+ { x: 170, y: 70, pred: 69 },
1686
+ { x: 180, y: 80, pred: 78 },
1687
+ { x: 190, y: 90, pred: 87 }
1688
+ ];
1689
+
1690
+ const padding = 60;
1691
+ const chartWidth = width - 2 * padding;
1692
+ const chartHeight = height - 2 * padding;
1693
+
1694
+ const xMin = 140, xMax = 200, yMin = 40, yMax = 100;
1695
+ const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth;
1696
+ const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight;
1697
+
1698
+ // Mean
1699
+ const mean = r2data.reduce((sum, p) => sum + p.y, 0) / r2data.length;
1700
+
1701
+ // Draw mean line
1702
+ ctx.strokeStyle = '#ff8c6a';
1703
+ ctx.setLineDash([5, 5]);
1704
+ ctx.lineWidth = 2;
1705
+ ctx.beginPath();
1706
+ ctx.moveTo(padding, scaleY(mean));
1707
+ ctx.lineTo(width - padding, scaleY(mean));
1708
+ ctx.stroke();
1709
+ ctx.setLineDash([]);
1710
+
1711
+ // Draw regression line
1712
+ ctx.strokeStyle = '#6aa9ff';
1713
+ ctx.lineWidth = 2;
1714
+ ctx.beginPath();
1715
+ ctx.moveTo(scaleX(xMin), scaleY(40));
1716
+ ctx.lineTo(scaleX(xMax), scaleY(95));
1717
+ ctx.stroke();
1718
+
1719
+ // Draw points
1720
+ r2data.forEach(p => {
1721
+ // Residual line
1722
+ ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)';
1723
+ ctx.lineWidth = 1;
1724
+ ctx.beginPath();
1725
+ ctx.moveTo(scaleX(p.x), scaleY(p.y));
1726
+ ctx.lineTo(scaleX(p.x), scaleY(p.pred));
1727
+ ctx.stroke();
1728
+
1729
+ // Actual point
1730
+ ctx.fillStyle = '#7ef0d4';
1731
+ ctx.beginPath();
1732
+ ctx.arc(scaleX(p.x), scaleY(p.y), 6, 0, 2 * Math.PI);
1733
+ ctx.fill();
1734
+ });
1735
+
1736
+ // Calculate R²
1737
+ let ssRes = 0, ssTot = 0;
1738
+ r2data.forEach(p => {
1739
+ ssRes += Math.pow(p.y - p.pred, 2);
1740
+ ssTot += Math.pow(p.y - mean, 2);
1741
+ });
1742
+ const r2 = 1 - (ssRes / ssTot);
1743
+
1744
+ // Info
1745
+ ctx.fillStyle = '#7ef0d4';
1746
+ ctx.font = '16px sans-serif';
1747
+ ctx.textAlign = 'left';
1748
+ ctx.fillText(`R² = ${r2.toFixed(3)}`, padding + 10, padding + 25);
1749
+ ctx.fillText(`Model explains ${(r2 * 100).toFixed(1)}% of variance`, padding + 10, padding + 50);
1750
+ }
1751
+
1752
+ // Regularization
1753
+ let regState = { lambda: 0.1 };
1754
+
1755
+ function initRegularization() {
1756
+ const canvas = document.getElementById('regularization-canvas');
1757
+ if (!canvas || canvas.dataset.initialized) return;
1758
+ canvas.dataset.initialized = 'true';
1759
+
1760
+ const slider = document.getElementById('reg-lambda-slider');
1761
+ if (slider) {
1762
+ slider.addEventListener('input', (e) => {
1763
+ regState.lambda = parseFloat(e.target.value);
1764
+ document.getElementById('reg-lambda-val').textContent = regState.lambda.toFixed(1);
1765
+ drawRegularization();
1766
+ });
1767
+ }
1768
+
1769
+ drawRegularization();
1770
+ }
1771
+
1772
+ function drawRegularization() {
1773
+ const canvas = document.getElementById('regularization-canvas');
1774
+ if (!canvas) return;
1775
+
1776
+ const ctx = canvas.getContext('2d');
1777
+ const width = canvas.width = canvas.offsetWidth;
1778
+ const height = canvas.height = 400;
1779
+
1780
+ ctx.clearRect(0, 0, width, height);
1781
+ ctx.fillStyle = '#1a2332';
1782
+ ctx.fillRect(0, 0, width, height);
1783
+
1784
+ const padding = 60;
1785
+ const chartWidth = width - 2 * padding;
1786
+ const chartHeight = height - 2 * padding;
1787
+
1788
+ const features = ['x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10'];
1789
+ const vanilla = [100, 200, 300, 50, 150, 250, 80, 120, 90, 180];
1790
+
1791
+ // Simulate L1 and L2 effects
1792
+ const l1 = vanilla.map(v => Math.abs(v) > 50 / regState.lambda ? v * (1 - regState.lambda * 0.5) : 0);
1793
+ const l2 = vanilla.map(v => v / (1 + regState.lambda));
1794
+
1795
+ const barWidth = chartWidth / (features.length * 3.5);
1796
+ const maxVal = Math.max(...vanilla);
1797
+
1798
+ features.forEach((f, i) => {
1799
+ const x = padding + (i * chartWidth / features.length);
1800
+
1801
+ // Vanilla
1802
+ const h1 = (vanilla[i] / maxVal) * chartHeight * 0.8;
1803
+ ctx.fillStyle = '#a9b4c2';
1804
+ ctx.fillRect(x, height - padding - h1, barWidth, h1);
1805
+
1806
+ // L1
1807
+ const h2 = (l1[i] / maxVal) * chartHeight * 0.8;
1808
+ ctx.fillStyle = '#ff8c6a';
1809
+ ctx.fillRect(x + barWidth * 1.2, height - padding - h2, barWidth, h2);
1810
+
1811
+ // L2
1812
+ const h3 = (l2[i] / maxVal) * chartHeight * 0.8;
1813
+ ctx.fillStyle = '#6aa9ff';
1814
+ ctx.fillRect(x + barWidth * 2.4, height - padding - h3, barWidth, h3);
1815
+
1816
+ // Feature label
1817
+ ctx.fillStyle = '#a9b4c2';
1818
+ ctx.font = '11px sans-serif';
1819
+ ctx.textAlign = 'center';
1820
+ ctx.fillText(f, x + barWidth * 1.5, height - padding + 20);
1821
+ });
1822
+
1823
+ // Legend
1824
+ const legendY = padding + 20;
1825
+ ctx.fillStyle = '#a9b4c2';
1826
+ ctx.fillRect(padding + 10, legendY, 15, 15);
1827
+ ctx.fillStyle = '#e8eef6';
1828
+ ctx.font = '12px sans-serif';
1829
+ ctx.textAlign = 'left';
1830
+ ctx.fillText('Vanilla', padding + 30, legendY + 12);
1831
+
1832
+ ctx.fillStyle = '#ff8c6a';
1833
+ ctx.fillRect(padding + 100, legendY, 15, 15);
1834
+ ctx.fillStyle = '#e8eef6';
1835
+ ctx.fillText('L1 (Lasso)', padding + 120, legendY + 12);
1836
+
1837
+ ctx.fillStyle = '#6aa9ff';
1838
+ ctx.fillRect(padding + 210, legendY, 15, 15);
1839
+ ctx.fillStyle = '#e8eef6';
1840
+ ctx.fillText('L2 (Ridge)', padding + 230, legendY + 12);
1841
+ }
1842
+
1843
+ // Bias-Variance
1844
+ function initBiasVariance() {
1845
+ const canvas = document.getElementById('bias-variance-canvas');
1846
+ if (!canvas || canvas.dataset.initialized) return;
1847
+ canvas.dataset.initialized = 'true';
1848
+ drawBiasVariance();
1849
+
1850
+ const canvas2 = document.getElementById('complexity-canvas');
1851
+ if (canvas2 && !canvas2.dataset.initialized) {
1852
+ canvas2.dataset.initialized = 'true';
1853
+ drawComplexityCurve();
1854
+ }
1855
+ }
1856
+
1857
+ function drawBiasVariance() {
1858
+ const canvas = document.getElementById('bias-variance-canvas');
1859
+ if (!canvas) return;
1860
+
1861
+ const ctx = canvas.getContext('2d');
1862
+ const width = canvas.width = canvas.offsetWidth;
1863
+ const height = canvas.height = 400;
1864
+
1865
+ ctx.clearRect(0, 0, width, height);
1866
+ ctx.fillStyle = '#1a2332';
1867
+ ctx.fillRect(0, 0, width, height);
1868
+
1869
+ const sectionWidth = width / 3;
1870
+ const padding = 40;
1871
+ const chartHeight = height - 2 * padding;
1872
+
1873
+ // Generate curved data
1874
+ const trueData = [];
1875
+ for (let x = 0; x <= 10; x += 0.5) {
1876
+ trueData.push({ x, y: 50 + 30 * Math.sin(x / 2) });
1877
+ }
1878
+
1879
+ // Draw three scenarios
1880
+ const scenarios = [
1881
+ { title: 'High Bias\n(Underfit)', color: '#ff8c6a', degree: 1 },
1882
+ { title: 'Good Fit', color: '#7ef0d4', degree: 2 },
1883
+ { title: 'High Variance\n(Overfit)', color: '#ff8c6a', degree: 8 }
1884
+ ];
1885
+
1886
+ scenarios.forEach((scenario, idx) => {
1887
+ const offsetX = idx * sectionWidth;
1888
+ const scaleX = (x) => offsetX + padding + (x / 10) * (sectionWidth - 2 * padding);
1889
+ const scaleY = (y) => padding + chartHeight - ((y - 20) / 80) * chartHeight;
1890
+
1891
+ // Draw true curve
1892
+ ctx.strokeStyle = 'rgba(106, 169, 255, 0.3)';
1893
+ ctx.lineWidth = 2;
1894
+ ctx.beginPath();
1895
+ trueData.forEach((p, i) => {
1896
+ if (i === 0) ctx.moveTo(scaleX(p.x), scaleY(p.y));
1897
+ else ctx.lineTo(scaleX(p.x), scaleY(p.y));
1898
+ });
1899
+ ctx.stroke();
1900
+
1901
+ // Draw model fit
1902
+ ctx.strokeStyle = scenario.color;
1903
+ ctx.lineWidth = 3;
1904
+ ctx.beginPath();
1905
+ if (scenario.degree === 1) {
1906
+ // Straight line
1907
+ ctx.moveTo(scaleX(0), scaleY(50));
1908
+ ctx.lineTo(scaleX(10), scaleY(65));
1909
+ } else if (scenario.degree === 2) {
1910
+ // Good fit
1911
+ trueData.forEach((p, i) => {
1912
+ const noise = (Math.random() - 0.5) * 3;
1913
+ if (i === 0) ctx.moveTo(scaleX(p.x), scaleY(p.y + noise));
1914
+ else ctx.lineTo(scaleX(p.x), scaleY(p.y + noise));
1915
+ });
1916
+ } else {
1917
+ // Wiggly overfit
1918
+ for (let x = 0; x <= 10; x += 0.2) {
1919
+ const y = 50 + 30 * Math.sin(x / 2) + 15 * Math.sin(x * 2);
1920
+ if (x === 0) ctx.moveTo(scaleX(x), scaleY(y));
1921
+ else ctx.lineTo(scaleX(x), scaleY(y));
1922
+ }
1923
+ }
1924
+ ctx.stroke();
1925
+
1926
+ // Title
1927
+ ctx.fillStyle = scenario.color;
1928
+ ctx.font = 'bold 14px sans-serif';
1929
+ ctx.textAlign = 'center';
1930
+ const lines = scenario.title.split('\n');
1931
+ lines.forEach((line, i) => {
1932
+ ctx.fillText(line, offsetX + sectionWidth / 2, 20 + i * 18);
1933
+ });
1934
+ });
1935
+ }
1936
+
1937
+ function drawComplexityCurve() {
1938
+ const canvas = document.getElementById('complexity-canvas');
1939
+ if (!canvas) return;
1940
+
1941
+ const ctx = canvas.getContext('2d');
1942
+ const width = canvas.width = canvas.offsetWidth;
1943
+ const height = canvas.height = 350;
1944
+
1945
+ ctx.clearRect(0, 0, width, height);
1946
+ ctx.fillStyle = '#1a2332';
1947
+ ctx.fillRect(0, 0, width, height);
1948
+
1949
+ const padding = 60;
1950
+ const chartWidth = width - 2 * padding;
1951
+ const chartHeight = height - 2 * padding;
1952
+
1953
+ const scaleX = (x) => padding + (x / 10) * chartWidth;
1954
+ const scaleY = (y) => padding + chartHeight - (y / 100) * chartHeight;
1955
+
1956
+ // Draw curves
1957
+ ctx.strokeStyle = '#ff8c6a';
1958
+ ctx.lineWidth = 3;
1959
+ ctx.beginPath();
1960
+ for (let x = 0; x <= 10; x += 0.1) {
1961
+ const trainError = 80 * Math.exp(-x / 2) + 5;
1962
+ if (x === 0) ctx.moveTo(scaleX(x), scaleY(trainError));
1963
+ else ctx.lineTo(scaleX(x), scaleY(trainError));
1964
+ }
1965
+ ctx.stroke();
1966
+
1967
+ ctx.strokeStyle = '#6aa9ff';
1968
+ ctx.beginPath();
1969
+ for (let x = 0; x <= 10; x += 0.1) {
1970
+ const testError = 80 * Math.exp(-x / 2) + 5 + 15 * (x / 10) ** 2;
1971
+ if (x === 0) ctx.moveTo(scaleX(x), scaleY(testError));
1972
+ else ctx.lineTo(scaleX(x), scaleY(testError));
1973
+ }
1974
+ ctx.stroke();
1975
+
1976
+ // Sweet spot
1977
+ ctx.fillStyle = '#7ef0d4';
1978
+ ctx.beginPath();
1979
+ ctx.arc(scaleX(5), scaleY(18), 8, 0, 2 * Math.PI);
1980
+ ctx.fill();
1981
+
1982
+ // Legend
1983
+ ctx.fillStyle = '#ff8c6a';
1984
+ ctx.font = '12px sans-serif';
1985
+ ctx.textAlign = 'left';
1986
+ ctx.fillText('Training Error', padding + 10, padding + 20);
1987
+ ctx.fillStyle = '#6aa9ff';
1988
+ ctx.fillText('Test Error', padding + 10, padding + 40);
1989
+ ctx.fillStyle = '#7ef0d4';
1990
+ ctx.fillText('● Sweet Spot', padding + 10, padding + 60);
1991
+
1992
+ // Labels
1993
+ ctx.fillStyle = '#a9b4c2';
1994
+ ctx.textAlign = 'center';
1995
+ ctx.fillText('Model Complexity →', width / 2, height - 20);
1996
+ ctx.save();
1997
+ ctx.translate(20, height / 2);
1998
+ ctx.rotate(-Math.PI / 2);
1999
+ ctx.fillText('Error', 0, 0);
2000
+ ctx.restore();
2001
+ }
2002
+
2003
+ // Cross-Validation
2004
+ function initCrossValidation() {
2005
+ const canvas = document.getElementById('cv-canvas');
2006
+ if (!canvas || canvas.dataset.initialized) return;
2007
+ canvas.dataset.initialized = 'true';
2008
+ drawCrossValidation();
2009
+ }
2010
+
2011
+ function drawCrossValidation() {
2012
+ const canvas = document.getElementById('cv-canvas');
2013
+ if (!canvas) return;
2014
+
2015
+ const ctx = canvas.getContext('2d');
2016
+ const width = canvas.width = canvas.offsetWidth;
2017
+ const height = canvas.height = 400;
2018
+
2019
+ ctx.clearRect(0, 0, width, height);
2020
+ ctx.fillStyle = '#1a2332';
2021
+ ctx.fillRect(0, 0, width, height);
2022
+
2023
+ const blockSize = 50;
2024
+ const gap = 10;
2025
+ const numBlocks = 12;
2026
+ const k = 3;
2027
+ const blocksPerFold = numBlocks / k;
2028
+
2029
+ const startX = (width - (numBlocks * blockSize + (numBlocks - 1) * gap)) / 2;
2030
+
2031
+ const folds = [0.96, 0.84, 0.90];
2032
+
2033
+ for (let fold = 0; fold < k; fold++) {
2034
+ const offsetY = 80 + fold * 120;
2035
+
2036
+ // Fold label
2037
+ ctx.fillStyle = '#e8eef6';
2038
+ ctx.font = 'bold 14px sans-serif';
2039
+ ctx.textAlign = 'right';
2040
+ ctx.fillText(`Fold ${fold + 1}:`, startX - 20, offsetY + blockSize / 2 + 5);
2041
+
2042
+ // Draw blocks
2043
+ for (let i = 0; i < numBlocks; i++) {
2044
+ const x = startX + i * (blockSize + gap);
2045
+ const isFold = i >= fold * blocksPerFold && i < (fold + 1) * blocksPerFold;
2046
+
2047
+ ctx.fillStyle = isFold ? '#6aa9ff' : '#7ef0d4';
2048
+ ctx.fillRect(x, offsetY, blockSize, blockSize);
2049
+
2050
+ // Label
2051
+ ctx.fillStyle = '#1a2332';
2052
+ ctx.font = 'bold 12px sans-serif';
2053
+ ctx.textAlign = 'center';
2054
+ ctx.fillText(String.fromCharCode(65 + i), x + blockSize / 2, offsetY + blockSize / 2 + 5);
2055
+ }
2056
+
2057
+ // Accuracy
2058
+ ctx.fillStyle = '#7ef0d4';
2059
+ ctx.font = '14px sans-serif';
2060
+ ctx.textAlign = 'left';
2061
+ ctx.fillText(`Acc: ${folds[fold].toFixed(2)}`, startX + numBlocks * (blockSize + gap) + 20, offsetY + blockSize / 2 + 5);
2062
+ }
2063
+
2064
+ // Legend
2065
+ ctx.fillStyle = '#6aa9ff';
2066
+ ctx.fillRect(startX, 30, 30, 20);
2067
+ ctx.fillStyle = '#e8eef6';
2068
+ ctx.font = '12px sans-serif';
2069
+ ctx.textAlign = 'left';
2070
+ ctx.fillText('Test Set', startX + 40, 45);
2071
+
2072
+ ctx.fillStyle = '#7ef0d4';
2073
+ ctx.fillRect(startX + 120, 30, 30, 20);
2074
+ ctx.fillText('Training Set', startX + 160, 45);
2075
+
2076
+ // Final result
2077
+ const mean = folds.reduce((a, b) => a + b) / folds.length;
2078
+ const std = Math.sqrt(folds.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / folds.length);
2079
+
2080
+ ctx.fillStyle = '#7ef0d4';
2081
+ ctx.font = 'bold 16px sans-serif';
2082
+ ctx.textAlign = 'center';
2083
+ ctx.fillText(`Final Score: ${mean.toFixed(2)} ± ${std.toFixed(3)}`, width / 2, height - 20);
2084
+ }
2085
+
2086
+ // Preprocessing
2087
+ function initPreprocessing() {
2088
+ const canvas = document.getElementById('scaling-canvas');
2089
+ if (canvas && !canvas.dataset.initialized) {
2090
+ canvas.dataset.initialized = 'true';
2091
+ drawScaling();
2092
+ }
2093
+
2094
+ const canvas2 = document.getElementById('pipeline-canvas');
2095
+ if (canvas2 && !canvas2.dataset.initialized) {
2096
+ canvas2.dataset.initialized = 'true';
2097
+ drawPipeline();
2098
+ }
2099
+ }
2100
+
2101
+ function drawScaling() {
2102
+ const canvas = document.getElementById('scaling-canvas');
2103
+ if (!canvas) return;
2104
+
2105
+ const ctx = canvas.getContext('2d');
2106
+ const width = canvas.width = canvas.offsetWidth;
2107
+ const height = canvas.height = 350;
2108
+
2109
+ ctx.clearRect(0, 0, width, height);
2110
+ ctx.fillStyle = '#1a2332';
2111
+ ctx.fillRect(0, 0, width, height);
2112
+
2113
+ const before = [10, 20, 30, 40, 50];
2114
+ const standard = [-1.26, -0.63, 0, 0.63, 1.26];
2115
+ const minmax = [0, 0.25, 0.5, 0.75, 1.0];
2116
+
2117
+ const sectionWidth = width / 3;
2118
+ const padding = 40;
2119
+ const barWidth = 30;
2120
+
2121
+ const datasets = [
2122
+ { data: before, title: 'Original', maxVal: 60 },
2123
+ { data: standard, title: 'StandardScaler', maxVal: 2 },
2124
+ { data: minmax, title: 'MinMaxScaler', maxVal: 1.2 }
2125
+ ];
2126
+
2127
+ datasets.forEach((dataset, idx) => {
2128
+ const offsetX = idx * sectionWidth;
2129
+ const centerX = offsetX + sectionWidth / 2;
2130
+
2131
+ // Title
2132
+ ctx.fillStyle = '#7ef0d4';
2133
+ ctx.font = 'bold 14px sans-serif';
2134
+ ctx.textAlign = 'center';
2135
+ ctx.fillText(dataset.title, centerX, 30);
2136
+
2137
+ // Draw bars
2138
+ dataset.data.forEach((val, i) => {
2139
+ const barHeight = Math.abs(val) / dataset.maxVal * 200;
2140
+ const x = centerX - barWidth / 2;
2141
+ const y = val >= 0 ? 200 - barHeight : 200;
2142
+
2143
+ ctx.fillStyle = '#6aa9ff';
2144
+ ctx.fillRect(x, y, barWidth, barHeight);
2145
+
2146
+ // Value label
2147
+ ctx.fillStyle = '#a9b4c2';
2148
+ ctx.font = '10px sans-serif';
2149
+ ctx.textAlign = 'center';
2150
+ ctx.fillText(val.toFixed(2), centerX, val >= 0 ? y - 5 : y + barHeight + 15);
2151
+
2152
+ centerX += 35;
2153
+ });
2154
+ });
2155
+ }
2156
+
2157
+ function drawPipeline() {
2158
+ const canvas = document.getElementById('pipeline-canvas');
2159
+ if (!canvas) return;
2160
+
2161
+ const ctx = canvas.getContext('2d');
2162
+ const width = canvas.width = canvas.offsetWidth;
2163
+ const height = canvas.height = 300;
2164
+
2165
+ ctx.clearRect(0, 0, width, height);
2166
+ ctx.fillStyle = '#1a2332';
2167
+ ctx.fillRect(0, 0, width, height);
2168
+
2169
+ const steps = ['Raw Data', 'Handle Missing', 'Encode Categories', 'Scale Features', 'Train Model'];
2170
+ const stepWidth = (width - 100) / steps.length;
2171
+ const y = height / 2;
2172
+
2173
+ steps.forEach((step, i) => {
2174
+ const x = 50 + i * stepWidth;
2175
+
2176
+ // Box
2177
+ ctx.fillStyle = '#2a3544';
2178
+ ctx.fillRect(x, y - 30, stepWidth - 40, 60);
2179
+ ctx.strokeStyle = '#6aa9ff';
2180
+ ctx.lineWidth = 2;
2181
+ ctx.strokeRect(x, y - 30, stepWidth - 40, 60);
2182
+
2183
+ // Text
2184
+ ctx.fillStyle = '#e8eef6';
2185
+ ctx.font = '12px sans-serif';
2186
+ ctx.textAlign = 'center';
2187
+ const words = step.split(' ');
2188
+ words.forEach((word, j) => {
2189
+ ctx.fillText(word, x + (stepWidth - 40) / 2, y + j * 15 - 5);
2190
+ });
2191
+
2192
+ // Arrow
2193
+ if (i < steps.length - 1) {
2194
+ ctx.strokeStyle = '#7ef0d4';
2195
+ ctx.lineWidth = 2;
2196
+ ctx.beginPath();
2197
+ ctx.moveTo(x + stepWidth - 40, y);
2198
+ ctx.lineTo(x + stepWidth - 10, y);
2199
+ ctx.stroke();
2200
+
2201
+ // Arrowhead
2202
+ ctx.fillStyle = '#7ef0d4';
2203
+ ctx.beginPath();
2204
+ ctx.moveTo(x + stepWidth - 10, y);
2205
+ ctx.lineTo(x + stepWidth - 20, y - 5);
2206
+ ctx.lineTo(x + stepWidth - 20, y + 5);
2207
+ ctx.fill();
2208
+ }
2209
+ });
2210
+ }
2211
+
2212
+ // Loss Functions
2213
+ function initLossFunctions() {
2214
+ const canvas = document.getElementById('loss-comparison-canvas');
2215
+ if (canvas && !canvas.dataset.initialized) {
2216
+ canvas.dataset.initialized = 'true';
2217
+ drawLossComparison();
2218
+ }
2219
+
2220
+ const canvas2 = document.getElementById('loss-curves-canvas');
2221
+ if (canvas2 && !canvas2.dataset.initialized) {
2222
+ canvas2.dataset.initialized = 'true';
2223
+ drawLossCurves();
2224
+ }
2225
+ }
2226
+
2227
+ function drawLossComparison() {
2228
+ const canvas = document.getElementById('loss-comparison-canvas');
2229
+ if (!canvas) return;
2230
+
2231
+ const ctx = canvas.getContext('2d');
2232
+ const width = canvas.width = canvas.offsetWidth;
2233
+ const height = canvas.height = 400;
2234
+
2235
+ ctx.clearRect(0, 0, width, height);
2236
+ ctx.fillStyle = '#1a2332';
2237
+ ctx.fillRect(0, 0, width, height);
2238
+
2239
+ const actual = [10, 20, 30, 40, 50];
2240
+ const predicted = [12, 19, 32, 38, 51];
2241
+
2242
+ // Calculate losses
2243
+ let mse = 0, mae = 0;
2244
+ actual.forEach((a, i) => {
2245
+ const error = a - predicted[i];
2246
+ mse += error * error;
2247
+ mae += Math.abs(error);
2248
+ });
2249
+ mse /= actual.length;
2250
+ mae /= actual.length;
2251
+ const rmse = Math.sqrt(mse);
2252
+
2253
+ // Display
2254
+ const padding = 60;
2255
+ const barHeight = 60;
2256
+ const startY = 100;
2257
+ const maxWidth = width - 2 * padding;
2258
+
2259
+ const losses = [
2260
+ { name: 'MSE', value: mse, color: '#ff8c6a' },
2261
+ { name: 'MAE', value: mae, color: '#6aa9ff' },
2262
+ { name: 'RMSE', value: rmse, color: '#7ef0d4' }
2263
+ ];
2264
+
2265
+ const maxLoss = Math.max(...losses.map(l => l.value));
2266
+
2267
+ losses.forEach((loss, i) => {
2268
+ const y = startY + i * (barHeight + 30);
2269
+ const barWidth = (loss.value / maxLoss) * maxWidth;
2270
+
2271
+ // Bar
2272
+ ctx.fillStyle = loss.color;
2273
+ ctx.fillRect(padding, y, barWidth, barHeight);
2274
+
2275
+ // Label
2276
+ ctx.fillStyle = '#e8eef6';
2277
+ ctx.font = 'bold 14px sans-serif';
2278
+ ctx.textAlign = 'left';
2279
+ ctx.fillText(loss.name, padding + 10, y + barHeight / 2 + 5);
2280
+
2281
+ // Value
2282
+ ctx.font = '16px sans-serif';
2283
+ ctx.textAlign = 'right';
2284
+ ctx.fillText(loss.value.toFixed(2), padding + barWidth - 10, y + barHeight / 2 + 5);
2285
+ });
2286
+
2287
+ // Title
2288
+ ctx.fillStyle = '#7ef0d4';
2289
+ ctx.font = 'bold 16px sans-serif';
2290
+ ctx.textAlign = 'center';
2291
+ ctx.fillText('Regression Loss Comparison', width / 2, 50);
2292
+ }
2293
+
2294
+ function drawLossCurves() {
2295
+ const canvas = document.getElementById('loss-curves-canvas');
2296
+ if (!canvas) return;
2297
+
2298
+ const ctx = canvas.getContext('2d');
2299
+ const width = canvas.width = canvas.offsetWidth;
2300
+ const height = canvas.height = 350;
2301
+
2302
+ ctx.clearRect(0, 0, width, height);
2303
+ ctx.fillStyle = '#1a2332';
2304
+ ctx.fillRect(0, 0, width, height);
2305
+
2306
+ const padding = 60;
2307
+ const chartWidth = width - 2 * padding;
2308
+ const chartHeight = height - 2 * padding;
2309
+
2310
+ const scaleX = (x) => padding + (x / 10) * chartWidth;
2311
+ const scaleY = (y) => height - padding - (y / 100) * chartHeight;
2312
+
2313
+ // Draw MSE curve
2314
+ ctx.strokeStyle = '#ff8c6a';
2315
+ ctx.lineWidth = 3;
2316
+ ctx.beginPath();
2317
+ for (let x = -10; x <= 10; x += 0.2) {
2318
+ const y = x * x;
2319
+ if (x === -10) ctx.moveTo(scaleX(x + 10), scaleY(y));
2320
+ else ctx.lineTo(scaleX(x + 10), scaleY(y));
2321
+ }
2322
+ ctx.stroke();
2323
+
2324
+ // Draw MAE curve
2325
+ ctx.strokeStyle = '#6aa9ff';
2326
+ ctx.lineWidth = 3;
2327
+ ctx.beginPath();
2328
+ for (let x = -10; x <= 10; x += 0.2) {
2329
+ const y = Math.abs(x) * 10;
2330
+ if (x === -10) ctx.moveTo(scaleX(x + 10), scaleY(y));
2331
+ else ctx.lineTo(scaleX(x + 10), scaleY(y));
2332
+ }
2333
+ ctx.stroke();
2334
+
2335
+ // Legend
2336
+ ctx.fillStyle = '#ff8c6a';
2337
+ ctx.font = '12px sans-serif';
2338
+ ctx.textAlign = 'left';
2339
+ ctx.fillText('MSE (quadratic penalty)', padding + 10, padding + 20);
2340
+ ctx.fillStyle = '#6aa9ff';
2341
+ ctx.fillText('MAE (linear penalty)', padding + 10, padding + 40);
2342
+
2343
+ // Labels
2344
+ ctx.fillStyle = '#a9b4c2';
2345
+ ctx.textAlign = 'center';
2346
+ ctx.fillText('Error', width / 2, height - 20);
2347
+ ctx.save();
2348
+ ctx.translate(20, height / 2);
2349
+ ctx.rotate(-Math.PI / 2);
2350
+ ctx.fillText('Loss', 0, 0);
2351
+ ctx.restore();
2352
+ }
2353
+
2354
+ // Handle window resize
2355
+ let resizeTimer;
2356
+ window.addEventListener('resize', () => {
2357
+ clearTimeout(resizeTimer);
2358
+ resizeTimer = setTimeout(() => {
2359
+ drawLinearRegression();
2360
+ drawGradientDescent();
2361
+ drawSigmoid();
2362
+ drawLogisticClassification();
2363
+ drawKNN();
2364
+ drawConfusionMatrix();
2365
+ drawROC();
2366
+ drawR2();
2367
+ drawRegularization();
2368
+ drawBiasVariance();
2369
+ drawComplexityCurve();
2370
+ drawCrossValidation();
2371
+ drawScaling();
2372
+ drawPipeline();
2373
+ drawLossComparison();
2374
+ drawLossCurves();
2375
+ drawSVMBasic();
2376
+ drawSVMMargin();
2377
+ drawSVMCParameter();
2378
+ drawSVMTraining();
2379
+ drawSVMKernel();
2380
+ }, 250);
2381
+ });
ml-complete-all-topics/index.html ADDED
The diff for this file is too large to render. See raw diff
 
ml-complete-all-topics/style.css ADDED
@@ -0,0 +1,1532 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ /* Primitive Color Tokens */
3
+ --color-white: rgba(255, 255, 255, 1);
4
+ --color-black: rgba(0, 0, 0, 1);
5
+ --color-cream-50: rgba(252, 252, 249, 1);
6
+ --color-cream-100: rgba(255, 255, 253, 1);
7
+ --color-gray-200: rgba(245, 245, 245, 1);
8
+ --color-gray-300: rgba(167, 169, 169, 1);
9
+ --color-gray-400: rgba(119, 124, 124, 1);
10
+ --color-slate-500: rgba(98, 108, 113, 1);
11
+ --color-brown-600: rgba(94, 82, 64, 1);
12
+ --color-charcoal-700: rgba(31, 33, 33, 1);
13
+ --color-charcoal-800: rgba(38, 40, 40, 1);
14
+ --color-slate-900: rgba(19, 52, 59, 1);
15
+ --color-teal-300: rgba(50, 184, 198, 1);
16
+ --color-teal-400: rgba(45, 166, 178, 1);
17
+ --color-teal-500: rgba(33, 128, 141, 1);
18
+ --color-teal-600: rgba(29, 116, 128, 1);
19
+ --color-teal-700: rgba(26, 104, 115, 1);
20
+ --color-teal-800: rgba(41, 150, 161, 1);
21
+ --color-red-400: rgba(255, 84, 89, 1);
22
+ --color-red-500: rgba(192, 21, 47, 1);
23
+ --color-orange-400: rgba(230, 129, 97, 1);
24
+ --color-orange-500: rgba(168, 75, 47, 1);
25
+
26
+ /* RGB versions for opacity control */
27
+ --color-brown-600-rgb: 94, 82, 64;
28
+ --color-teal-500-rgb: 33, 128, 141;
29
+ --color-slate-900-rgb: 19, 52, 59;
30
+ --color-slate-500-rgb: 98, 108, 113;
31
+ --color-red-500-rgb: 192, 21, 47;
32
+ --color-red-400-rgb: 255, 84, 89;
33
+ --color-orange-500-rgb: 168, 75, 47;
34
+ --color-orange-400-rgb: 230, 129, 97;
35
+
36
+ /* Background color tokens (Light Mode) */
37
+ --color-bg-1: rgba(59, 130, 246, 0.08); /* Light blue */
38
+ --color-bg-2: rgba(245, 158, 11, 0.08); /* Light yellow */
39
+ --color-bg-3: rgba(34, 197, 94, 0.08); /* Light green */
40
+ --color-bg-4: rgba(239, 68, 68, 0.08); /* Light red */
41
+ --color-bg-5: rgba(147, 51, 234, 0.08); /* Light purple */
42
+ --color-bg-6: rgba(249, 115, 22, 0.08); /* Light orange */
43
+ --color-bg-7: rgba(236, 72, 153, 0.08); /* Light pink */
44
+ --color-bg-8: rgba(6, 182, 212, 0.08); /* Light cyan */
45
+
46
+ /* Semantic Color Tokens (Light Mode) */
47
+ --color-background: var(--color-cream-50);
48
+ --color-surface: var(--color-cream-100);
49
+ --color-text: var(--color-slate-900);
50
+ --color-text-secondary: var(--color-slate-500);
51
+ --color-primary: var(--color-teal-500);
52
+ --color-primary-hover: var(--color-teal-600);
53
+ --color-primary-active: var(--color-teal-700);
54
+ --color-secondary: rgba(var(--color-brown-600-rgb), 0.12);
55
+ --color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2);
56
+ --color-secondary-active: rgba(var(--color-brown-600-rgb), 0.25);
57
+ --color-border: rgba(var(--color-brown-600-rgb), 0.2);
58
+ --color-btn-primary-text: var(--color-cream-50);
59
+ --color-card-border: rgba(var(--color-brown-600-rgb), 0.12);
60
+ --color-card-border-inner: rgba(var(--color-brown-600-rgb), 0.12);
61
+ --color-error: var(--color-red-500);
62
+ --color-success: var(--color-teal-500);
63
+ --color-warning: var(--color-orange-500);
64
+ --color-info: var(--color-slate-500);
65
+ --color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4);
66
+ --color-select-caret: rgba(var(--color-slate-900-rgb), 0.8);
67
+
68
+ /* Common style patterns */
69
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
70
+ --focus-outline: 2px solid var(--color-primary);
71
+ --status-bg-opacity: 0.15;
72
+ --status-border-opacity: 0.25;
73
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
74
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
75
+
76
+ /* RGB versions for opacity control */
77
+ --color-success-rgb: 33, 128, 141;
78
+ --color-error-rgb: 192, 21, 47;
79
+ --color-warning-rgb: 168, 75, 47;
80
+ --color-info-rgb: 98, 108, 113;
81
+
82
+ /* Typography */
83
+ --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system,
84
+ BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
85
+ --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo,
86
+ Monaco, Consolas, monospace;
87
+ --font-size-xs: 11px;
88
+ --font-size-sm: 12px;
89
+ --font-size-base: 14px;
90
+ --font-size-md: 14px;
91
+ --font-size-lg: 16px;
92
+ --font-size-xl: 18px;
93
+ --font-size-2xl: 20px;
94
+ --font-size-3xl: 24px;
95
+ --font-size-4xl: 30px;
96
+ --font-weight-normal: 400;
97
+ --font-weight-medium: 500;
98
+ --font-weight-semibold: 550;
99
+ --font-weight-bold: 600;
100
+ --line-height-tight: 1.2;
101
+ --line-height-normal: 1.5;
102
+ --letter-spacing-tight: -0.01em;
103
+
104
+ /* Spacing */
105
+ --space-0: 0;
106
+ --space-1: 1px;
107
+ --space-2: 2px;
108
+ --space-4: 4px;
109
+ --space-6: 6px;
110
+ --space-8: 8px;
111
+ --space-10: 10px;
112
+ --space-12: 12px;
113
+ --space-16: 16px;
114
+ --space-20: 20px;
115
+ --space-24: 24px;
116
+ --space-32: 32px;
117
+
118
+ /* Border Radius */
119
+ --radius-sm: 6px;
120
+ --radius-base: 8px;
121
+ --radius-md: 10px;
122
+ --radius-lg: 12px;
123
+ --radius-full: 9999px;
124
+
125
+ /* Shadows */
126
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02);
127
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
128
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04),
129
+ 0 2px 4px -1px rgba(0, 0, 0, 0.02);
130
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04),
131
+ 0 4px 6px -2px rgba(0, 0, 0, 0.02);
132
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15),
133
+ inset 0 -1px 0 rgba(0, 0, 0, 0.03);
134
+
135
+ /* Animation */
136
+ --duration-fast: 150ms;
137
+ --duration-normal: 250ms;
138
+ --ease-standard: cubic-bezier(0.16, 1, 0.3, 1);
139
+
140
+ /* Layout */
141
+ --container-sm: 640px;
142
+ --container-md: 768px;
143
+ --container-lg: 1024px;
144
+ --container-xl: 1280px;
145
+ }
146
+
147
+ /* Dark mode colors */
148
+ @media (prefers-color-scheme: dark) {
149
+ :root {
150
+ /* RGB versions for opacity control (Dark Mode) */
151
+ --color-gray-400-rgb: 119, 124, 124;
152
+ --color-teal-300-rgb: 50, 184, 198;
153
+ --color-gray-300-rgb: 167, 169, 169;
154
+ --color-gray-200-rgb: 245, 245, 245;
155
+
156
+ /* Background color tokens (Dark Mode) */
157
+ --color-bg-1: rgba(29, 78, 216, 0.15); /* Dark blue */
158
+ --color-bg-2: rgba(180, 83, 9, 0.15); /* Dark yellow */
159
+ --color-bg-3: rgba(21, 128, 61, 0.15); /* Dark green */
160
+ --color-bg-4: rgba(185, 28, 28, 0.15); /* Dark red */
161
+ --color-bg-5: rgba(107, 33, 168, 0.15); /* Dark purple */
162
+ --color-bg-6: rgba(194, 65, 12, 0.15); /* Dark orange */
163
+ --color-bg-7: rgba(190, 24, 93, 0.15); /* Dark pink */
164
+ --color-bg-8: rgba(8, 145, 178, 0.15); /* Dark cyan */
165
+
166
+ /* Semantic Color Tokens (Dark Mode) */
167
+ --color-background: var(--color-charcoal-700);
168
+ --color-surface: var(--color-charcoal-800);
169
+ --color-text: var(--color-gray-200);
170
+ --color-text-secondary: rgba(var(--color-gray-300-rgb), 0.7);
171
+ --color-primary: var(--color-teal-300);
172
+ --color-primary-hover: var(--color-teal-400);
173
+ --color-primary-active: var(--color-teal-800);
174
+ --color-secondary: rgba(var(--color-gray-400-rgb), 0.15);
175
+ --color-secondary-hover: rgba(var(--color-gray-400-rgb), 0.25);
176
+ --color-secondary-active: rgba(var(--color-gray-400-rgb), 0.3);
177
+ --color-border: rgba(var(--color-gray-400-rgb), 0.3);
178
+ --color-error: var(--color-red-400);
179
+ --color-success: var(--color-teal-300);
180
+ --color-warning: var(--color-orange-400);
181
+ --color-info: var(--color-gray-300);
182
+ --color-focus-ring: rgba(var(--color-teal-300-rgb), 0.4);
183
+ --color-btn-primary-text: var(--color-slate-900);
184
+ --color-card-border: rgba(var(--color-gray-400-rgb), 0.2);
185
+ --color-card-border-inner: rgba(var(--color-gray-400-rgb), 0.15);
186
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1),
187
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
188
+ --button-border-secondary: rgba(var(--color-gray-400-rgb), 0.2);
189
+ --color-border-secondary: rgba(var(--color-gray-400-rgb), 0.2);
190
+ --color-select-caret: rgba(var(--color-gray-200-rgb), 0.8);
191
+
192
+ /* Common style patterns - updated for dark mode */
193
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
194
+ --focus-outline: 2px solid var(--color-primary);
195
+ --status-bg-opacity: 0.15;
196
+ --status-border-opacity: 0.25;
197
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
198
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
199
+
200
+ /* RGB versions for dark mode */
201
+ --color-success-rgb: var(--color-teal-300-rgb);
202
+ --color-error-rgb: var(--color-red-400-rgb);
203
+ --color-warning-rgb: var(--color-orange-400-rgb);
204
+ --color-info-rgb: var(--color-gray-300-rgb);
205
+ }
206
+ }
207
+
208
+ /* Data attribute for manual theme switching */
209
+ [data-color-scheme="dark"] {
210
+ /* RGB versions for opacity control (dark mode) */
211
+ --color-gray-400-rgb: 119, 124, 124;
212
+ --color-teal-300-rgb: 50, 184, 198;
213
+ --color-gray-300-rgb: 167, 169, 169;
214
+ --color-gray-200-rgb: 245, 245, 245;
215
+
216
+ /* Colorful background palette - Dark Mode */
217
+ --color-bg-1: rgba(29, 78, 216, 0.15); /* Dark blue */
218
+ --color-bg-2: rgba(180, 83, 9, 0.15); /* Dark yellow */
219
+ --color-bg-3: rgba(21, 128, 61, 0.15); /* Dark green */
220
+ --color-bg-4: rgba(185, 28, 28, 0.15); /* Dark red */
221
+ --color-bg-5: rgba(107, 33, 168, 0.15); /* Dark purple */
222
+ --color-bg-6: rgba(194, 65, 12, 0.15); /* Dark orange */
223
+ --color-bg-7: rgba(190, 24, 93, 0.15); /* Dark pink */
224
+ --color-bg-8: rgba(8, 145, 178, 0.15); /* Dark cyan */
225
+
226
+ /* Semantic Color Tokens (Dark Mode) */
227
+ --color-background: var(--color-charcoal-700);
228
+ --color-surface: var(--color-charcoal-800);
229
+ --color-text: var(--color-gray-200);
230
+ --color-text-secondary: rgba(var(--color-gray-300-rgb), 0.7);
231
+ --color-primary: var(--color-teal-300);
232
+ --color-primary-hover: var(--color-teal-400);
233
+ --color-primary-active: var(--color-teal-800);
234
+ --color-secondary: rgba(var(--color-gray-400-rgb), 0.15);
235
+ --color-secondary-hover: rgba(var(--color-gray-400-rgb), 0.25);
236
+ --color-secondary-active: rgba(var(--color-gray-400-rgb), 0.3);
237
+ --color-border: rgba(var(--color-gray-400-rgb), 0.3);
238
+ --color-error: var(--color-red-400);
239
+ --color-success: var(--color-teal-300);
240
+ --color-warning: var(--color-orange-400);
241
+ --color-info: var(--color-gray-300);
242
+ --color-focus-ring: rgba(var(--color-teal-300-rgb), 0.4);
243
+ --color-btn-primary-text: var(--color-slate-900);
244
+ --color-card-border: rgba(var(--color-gray-400-rgb), 0.15);
245
+ --color-card-border-inner: rgba(var(--color-gray-400-rgb), 0.15);
246
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1),
247
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
248
+ --color-border-secondary: rgba(var(--color-gray-400-rgb), 0.2);
249
+ --color-select-caret: rgba(var(--color-gray-200-rgb), 0.8);
250
+
251
+ /* Common style patterns - updated for dark mode */
252
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
253
+ --focus-outline: 2px solid var(--color-primary);
254
+ --status-bg-opacity: 0.15;
255
+ --status-border-opacity: 0.25;
256
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
257
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
258
+
259
+ /* RGB versions for dark mode */
260
+ --color-success-rgb: var(--color-teal-300-rgb);
261
+ --color-error-rgb: var(--color-red-400-rgb);
262
+ --color-warning-rgb: var(--color-orange-400-rgb);
263
+ --color-info-rgb: var(--color-gray-300-rgb);
264
+ }
265
+
266
+ [data-color-scheme="light"] {
267
+ /* RGB versions for opacity control (light mode) */
268
+ --color-brown-600-rgb: 94, 82, 64;
269
+ --color-teal-500-rgb: 33, 128, 141;
270
+ --color-slate-900-rgb: 19, 52, 59;
271
+
272
+ /* Semantic Color Tokens (Light Mode) */
273
+ --color-background: var(--color-cream-50);
274
+ --color-surface: var(--color-cream-100);
275
+ --color-text: var(--color-slate-900);
276
+ --color-text-secondary: var(--color-slate-500);
277
+ --color-primary: var(--color-teal-500);
278
+ --color-primary-hover: var(--color-teal-600);
279
+ --color-primary-active: var(--color-teal-700);
280
+ --color-secondary: rgba(var(--color-brown-600-rgb), 0.12);
281
+ --color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2);
282
+ --color-secondary-active: rgba(var(--color-brown-600-rgb), 0.25);
283
+ --color-border: rgba(var(--color-brown-600-rgb), 0.2);
284
+ --color-btn-primary-text: var(--color-cream-50);
285
+ --color-card-border: rgba(var(--color-brown-600-rgb), 0.12);
286
+ --color-card-border-inner: rgba(var(--color-brown-600-rgb), 0.12);
287
+ --color-error: var(--color-red-500);
288
+ --color-success: var(--color-teal-500);
289
+ --color-warning: var(--color-orange-500);
290
+ --color-info: var(--color-slate-500);
291
+ --color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4);
292
+
293
+ /* RGB versions for light mode */
294
+ --color-success-rgb: var(--color-teal-500-rgb);
295
+ --color-error-rgb: var(--color-red-500-rgb);
296
+ --color-warning-rgb: var(--color-orange-500-rgb);
297
+ --color-info-rgb: var(--color-slate-500-rgb);
298
+ }
299
+
300
+ /* Base styles */
301
+ html {
302
+ font-size: var(--font-size-base);
303
+ font-family: var(--font-family-base);
304
+ line-height: var(--line-height-normal);
305
+ color: var(--color-text);
306
+ background-color: var(--color-background);
307
+ -webkit-font-smoothing: antialiased;
308
+ box-sizing: border-box;
309
+ }
310
+
311
+ body {
312
+ margin: 0;
313
+ padding: 0;
314
+ }
315
+
316
+ *,
317
+ *::before,
318
+ *::after {
319
+ box-sizing: inherit;
320
+ }
321
+
322
+ /* Typography */
323
+ h1,
324
+ h2,
325
+ h3,
326
+ h4,
327
+ h5,
328
+ h6 {
329
+ margin: 0;
330
+ font-weight: var(--font-weight-semibold);
331
+ line-height: var(--line-height-tight);
332
+ color: var(--color-text);
333
+ letter-spacing: var(--letter-spacing-tight);
334
+ }
335
+
336
+ h1 {
337
+ font-size: var(--font-size-4xl);
338
+ }
339
+ h2 {
340
+ font-size: var(--font-size-3xl);
341
+ }
342
+ h3 {
343
+ font-size: var(--font-size-2xl);
344
+ }
345
+ h4 {
346
+ font-size: var(--font-size-xl);
347
+ }
348
+ h5 {
349
+ font-size: var(--font-size-lg);
350
+ }
351
+ h6 {
352
+ font-size: var(--font-size-md);
353
+ }
354
+
355
+ p {
356
+ margin: 0 0 var(--space-16) 0;
357
+ }
358
+
359
+ a {
360
+ color: var(--color-primary);
361
+ text-decoration: none;
362
+ transition: color var(--duration-fast) var(--ease-standard);
363
+ }
364
+
365
+ a:hover {
366
+ color: var(--color-primary-hover);
367
+ }
368
+
369
+ code,
370
+ pre {
371
+ font-family: var(--font-family-mono);
372
+ font-size: calc(var(--font-size-base) * 0.95);
373
+ background-color: var(--color-secondary);
374
+ border-radius: var(--radius-sm);
375
+ }
376
+
377
+ code {
378
+ padding: var(--space-1) var(--space-4);
379
+ }
380
+
381
+ pre {
382
+ padding: var(--space-16);
383
+ margin: var(--space-16) 0;
384
+ overflow: auto;
385
+ border: 1px solid var(--color-border);
386
+ }
387
+
388
+ pre code {
389
+ background: none;
390
+ padding: 0;
391
+ }
392
+
393
+ /* Buttons */
394
+ .btn {
395
+ display: inline-flex;
396
+ align-items: center;
397
+ justify-content: center;
398
+ padding: var(--space-8) var(--space-16);
399
+ border-radius: var(--radius-base);
400
+ font-size: var(--font-size-base);
401
+ font-weight: 500;
402
+ line-height: 1.5;
403
+ cursor: pointer;
404
+ transition: all var(--duration-normal) var(--ease-standard);
405
+ border: none;
406
+ text-decoration: none;
407
+ position: relative;
408
+ }
409
+
410
+ .btn:focus-visible {
411
+ outline: none;
412
+ box-shadow: var(--focus-ring);
413
+ }
414
+
415
+ .btn--primary {
416
+ background: var(--color-primary);
417
+ color: var(--color-btn-primary-text);
418
+ }
419
+
420
+ .btn--primary:hover {
421
+ background: var(--color-primary-hover);
422
+ }
423
+
424
+ .btn--primary:active {
425
+ background: var(--color-primary-active);
426
+ }
427
+
428
+ .btn--secondary {
429
+ background: var(--color-secondary);
430
+ color: var(--color-text);
431
+ }
432
+
433
+ .btn--secondary:hover {
434
+ background: var(--color-secondary-hover);
435
+ }
436
+
437
+ .btn--secondary:active {
438
+ background: var(--color-secondary-active);
439
+ }
440
+
441
+ .btn--outline {
442
+ background: transparent;
443
+ border: 1px solid var(--color-border);
444
+ color: var(--color-text);
445
+ }
446
+
447
+ .btn--outline:hover {
448
+ background: var(--color-secondary);
449
+ }
450
+
451
+ .btn--sm {
452
+ padding: var(--space-4) var(--space-12);
453
+ font-size: var(--font-size-sm);
454
+ border-radius: var(--radius-sm);
455
+ }
456
+
457
+ .btn--lg {
458
+ padding: var(--space-10) var(--space-20);
459
+ font-size: var(--font-size-lg);
460
+ border-radius: var(--radius-md);
461
+ }
462
+
463
+ .btn--full-width {
464
+ width: 100%;
465
+ }
466
+
467
+ .btn:disabled {
468
+ opacity: 0.5;
469
+ cursor: not-allowed;
470
+ }
471
+
472
+ /* Form elements */
473
+ .form-control {
474
+ display: block;
475
+ width: 100%;
476
+ padding: var(--space-8) var(--space-12);
477
+ font-size: var(--font-size-md);
478
+ line-height: 1.5;
479
+ color: var(--color-text);
480
+ background-color: var(--color-surface);
481
+ border: 1px solid var(--color-border);
482
+ border-radius: var(--radius-base);
483
+ transition: border-color var(--duration-fast) var(--ease-standard),
484
+ box-shadow var(--duration-fast) var(--ease-standard);
485
+ }
486
+
487
+ textarea.form-control {
488
+ font-family: var(--font-family-base);
489
+ font-size: var(--font-size-base);
490
+ }
491
+
492
+ select.form-control {
493
+ padding: var(--space-8) var(--space-12);
494
+ -webkit-appearance: none;
495
+ -moz-appearance: none;
496
+ appearance: none;
497
+ background-image: var(--select-caret-light);
498
+ background-repeat: no-repeat;
499
+ background-position: right var(--space-12) center;
500
+ background-size: 16px;
501
+ padding-right: var(--space-32);
502
+ }
503
+
504
+ /* Add a dark mode specific caret */
505
+ @media (prefers-color-scheme: dark) {
506
+ select.form-control {
507
+ background-image: var(--select-caret-dark);
508
+ }
509
+ }
510
+
511
+ /* Also handle data-color-scheme */
512
+ [data-color-scheme="dark"] select.form-control {
513
+ background-image: var(--select-caret-dark);
514
+ }
515
+
516
+ [data-color-scheme="light"] select.form-control {
517
+ background-image: var(--select-caret-light);
518
+ }
519
+
520
+ .form-control:focus {
521
+ border-color: var(--color-primary);
522
+ outline: var(--focus-outline);
523
+ }
524
+
525
+ .form-label {
526
+ display: block;
527
+ margin-bottom: var(--space-8);
528
+ font-weight: var(--font-weight-medium);
529
+ font-size: var(--font-size-sm);
530
+ }
531
+
532
+ .form-group {
533
+ margin-bottom: var(--space-16);
534
+ }
535
+
536
+ /* Card component */
537
+ .card {
538
+ background-color: var(--color-surface);
539
+ border-radius: var(--radius-lg);
540
+ border: 1px solid var(--color-card-border);
541
+ box-shadow: var(--shadow-sm);
542
+ overflow: hidden;
543
+ transition: box-shadow var(--duration-normal) var(--ease-standard);
544
+ }
545
+
546
+ .card:hover {
547
+ box-shadow: var(--shadow-md);
548
+ }
549
+
550
+ .card__body {
551
+ padding: var(--space-16);
552
+ }
553
+
554
+ .card__header,
555
+ .card__footer {
556
+ padding: var(--space-16);
557
+ border-bottom: 1px solid var(--color-card-border-inner);
558
+ }
559
+
560
+ /* Status indicators - simplified with CSS variables */
561
+ .status {
562
+ display: inline-flex;
563
+ align-items: center;
564
+ padding: var(--space-6) var(--space-12);
565
+ border-radius: var(--radius-full);
566
+ font-weight: var(--font-weight-medium);
567
+ font-size: var(--font-size-sm);
568
+ }
569
+
570
+ .status--success {
571
+ background-color: rgba(
572
+ var(--color-success-rgb, 33, 128, 141),
573
+ var(--status-bg-opacity)
574
+ );
575
+ color: var(--color-success);
576
+ border: 1px solid
577
+ rgba(var(--color-success-rgb, 33, 128, 141), var(--status-border-opacity));
578
+ }
579
+
580
+ .status--error {
581
+ background-color: rgba(
582
+ var(--color-error-rgb, 192, 21, 47),
583
+ var(--status-bg-opacity)
584
+ );
585
+ color: var(--color-error);
586
+ border: 1px solid
587
+ rgba(var(--color-error-rgb, 192, 21, 47), var(--status-border-opacity));
588
+ }
589
+
590
+ .status--warning {
591
+ background-color: rgba(
592
+ var(--color-warning-rgb, 168, 75, 47),
593
+ var(--status-bg-opacity)
594
+ );
595
+ color: var(--color-warning);
596
+ border: 1px solid
597
+ rgba(var(--color-warning-rgb, 168, 75, 47), var(--status-border-opacity));
598
+ }
599
+
600
+ .status--info {
601
+ background-color: rgba(
602
+ var(--color-info-rgb, 98, 108, 113),
603
+ var(--status-bg-opacity)
604
+ );
605
+ color: var(--color-info);
606
+ border: 1px solid
607
+ rgba(var(--color-info-rgb, 98, 108, 113), var(--status-border-opacity));
608
+ }
609
+
610
+ /* Container layout */
611
+ .container {
612
+ width: 100%;
613
+ margin-right: auto;
614
+ margin-left: auto;
615
+ padding-right: var(--space-16);
616
+ padding-left: var(--space-16);
617
+ }
618
+
619
+ @media (min-width: 640px) {
620
+ .container {
621
+ max-width: var(--container-sm);
622
+ }
623
+ }
624
+ @media (min-width: 768px) {
625
+ .container {
626
+ max-width: var(--container-md);
627
+ }
628
+ }
629
+ @media (min-width: 1024px) {
630
+ .container {
631
+ max-width: var(--container-lg);
632
+ }
633
+ }
634
+ @media (min-width: 1280px) {
635
+ .container {
636
+ max-width: var(--container-xl);
637
+ }
638
+ }
639
+
640
+ /* Utility classes */
641
+ .flex {
642
+ display: flex;
643
+ }
644
+ .flex-col {
645
+ flex-direction: column;
646
+ }
647
+ .items-center {
648
+ align-items: center;
649
+ }
650
+ .justify-center {
651
+ justify-content: center;
652
+ }
653
+ .justify-between {
654
+ justify-content: space-between;
655
+ }
656
+ .gap-4 {
657
+ gap: var(--space-4);
658
+ }
659
+ .gap-8 {
660
+ gap: var(--space-8);
661
+ }
662
+ .gap-16 {
663
+ gap: var(--space-16);
664
+ }
665
+
666
+ .m-0 {
667
+ margin: 0;
668
+ }
669
+ .mt-8 {
670
+ margin-top: var(--space-8);
671
+ }
672
+ .mb-8 {
673
+ margin-bottom: var(--space-8);
674
+ }
675
+ .mx-8 {
676
+ margin-left: var(--space-8);
677
+ margin-right: var(--space-8);
678
+ }
679
+ .my-8 {
680
+ margin-top: var(--space-8);
681
+ margin-bottom: var(--space-8);
682
+ }
683
+
684
+ .p-0 {
685
+ padding: 0;
686
+ }
687
+ .py-8 {
688
+ padding-top: var(--space-8);
689
+ padding-bottom: var(--space-8);
690
+ }
691
+ .px-8 {
692
+ padding-left: var(--space-8);
693
+ padding-right: var(--space-8);
694
+ }
695
+ .py-16 {
696
+ padding-top: var(--space-16);
697
+ padding-bottom: var(--space-16);
698
+ }
699
+ .px-16 {
700
+ padding-left: var(--space-16);
701
+ padding-right: var(--space-16);
702
+ }
703
+
704
+ .block {
705
+ display: block;
706
+ }
707
+ .hidden {
708
+ display: none;
709
+ }
710
+
711
+ /* Accessibility */
712
+ .sr-only {
713
+ position: absolute;
714
+ width: 1px;
715
+ height: 1px;
716
+ padding: 0;
717
+ margin: -1px;
718
+ overflow: hidden;
719
+ clip: rect(0, 0, 0, 0);
720
+ white-space: nowrap;
721
+ border-width: 0;
722
+ }
723
+
724
+ :focus-visible {
725
+ outline: var(--focus-outline);
726
+ outline-offset: 2px;
727
+ }
728
+
729
+ /* Dark mode specifics */
730
+ [data-color-scheme="dark"] .btn--outline {
731
+ border: 1px solid var(--color-border-secondary);
732
+ }
733
+
734
+ @font-face {
735
+ font-family: 'FKGroteskNeue';
736
+ src: url('https://r2cdn.perplexity.ai/fonts/FKGroteskNeue.woff2')
737
+ format('woff2');
738
+ }
739
+
740
+ /* END PERPLEXITY DESIGN SYSTEM */
741
+ :root {
742
+ /* Primitive Color Tokens */
743
+ --color-white: rgba(255, 255, 255, 1);
744
+ --color-black: rgba(0, 0, 0, 1);
745
+ --color-cream-50: rgba(252, 252, 249, 1);
746
+ --color-cream-100: rgba(255, 255, 253, 1);
747
+ --color-gray-200: rgba(245, 245, 245, 1);
748
+ --color-gray-300: rgba(167, 169, 169, 1);
749
+ --color-gray-400: rgba(119, 124, 124, 1);
750
+ --color-slate-500: rgba(98, 108, 113, 1);
751
+ --color-brown-600: rgba(94, 82, 64, 1);
752
+ --color-charcoal-700: rgba(31, 33, 33, 1);
753
+ --color-charcoal-800: rgba(38, 40, 40, 1);
754
+ --color-slate-900: rgba(19, 52, 59, 1);
755
+ --color-teal-300: rgba(50, 184, 198, 1);
756
+ --color-teal-400: rgba(45, 166, 178, 1);
757
+ --color-teal-500: rgba(33, 128, 141, 1);
758
+ --color-teal-600: rgba(29, 116, 128, 1);
759
+ --color-teal-700: rgba(26, 104, 115, 1);
760
+ --color-teal-800: rgba(41, 150, 161, 1);
761
+ --color-red-400: rgba(255, 84, 89, 1);
762
+ --color-red-500: rgba(192, 21, 47, 1);
763
+ --color-orange-400: rgba(230, 129, 97, 1);
764
+ --color-orange-500: rgba(168, 75, 47, 1);
765
+
766
+ /* RGB versions for opacity control */
767
+ --color-brown-600-rgb: 94, 82, 64;
768
+ --color-teal-500-rgb: 33, 128, 141;
769
+ --color-slate-900-rgb: 19, 52, 59;
770
+ --color-slate-500-rgb: 98, 108, 113;
771
+ --color-red-500-rgb: 192, 21, 47;
772
+ --color-red-400-rgb: 255, 84, 89;
773
+ --color-orange-500-rgb: 168, 75, 47;
774
+ --color-orange-400-rgb: 230, 129, 97;
775
+
776
+ /* Background color tokens (Light Mode) */
777
+ --color-bg-1: rgba(59, 130, 246, 0.08);
778
+ --color-bg-2: rgba(245, 158, 11, 0.08);
779
+ --color-bg-3: rgba(34, 197, 94, 0.08);
780
+ --color-bg-4: rgba(239, 68, 68, 0.08);
781
+ --color-bg-5: rgba(147, 51, 234, 0.08);
782
+ --color-bg-6: rgba(249, 115, 22, 0.08);
783
+ --color-bg-7: rgba(236, 72, 153, 0.08);
784
+ --color-bg-8: rgba(6, 182, 212, 0.08);
785
+
786
+ /* Semantic Color Tokens (Light Mode) */
787
+ --color-background: var(--color-cream-50);
788
+ --color-surface: var(--color-cream-100);
789
+ --color-text: var(--color-slate-900);
790
+ --color-text-secondary: var(--color-slate-500);
791
+ --color-primary: var(--color-teal-500);
792
+ --color-primary-hover: var(--color-teal-600);
793
+ --color-primary-active: var(--color-teal-700);
794
+ --color-secondary: rgba(var(--color-brown-600-rgb), 0.12);
795
+ --color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2);
796
+ --color-secondary-active: rgba(var(--color-brown-600-rgb), 0.25);
797
+ --color-border: rgba(var(--color-brown-600-rgb), 0.2);
798
+ --color-btn-primary-text: var(--color-cream-50);
799
+ --color-card-border: rgba(var(--color-brown-600-rgb), 0.12);
800
+ --color-card-border-inner: rgba(var(--color-brown-600-rgb), 0.12);
801
+ --color-error: var(--color-red-500);
802
+ --color-success: var(--color-teal-500);
803
+ --color-warning: var(--color-orange-500);
804
+ --color-info: var(--color-slate-500);
805
+ --color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4);
806
+ --color-select-caret: rgba(var(--color-slate-900-rgb), 0.8);
807
+
808
+ /* Typography */
809
+ --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
810
+ --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
811
+ --font-size-xs: 11px;
812
+ --font-size-sm: 12px;
813
+ --font-size-base: 14px;
814
+ --font-size-md: 14px;
815
+ --font-size-lg: 16px;
816
+ --font-size-xl: 18px;
817
+ --font-size-2xl: 20px;
818
+ --font-size-3xl: 24px;
819
+ --font-size-4xl: 30px;
820
+ --font-weight-normal: 400;
821
+ --font-weight-medium: 500;
822
+ --font-weight-semibold: 550;
823
+ --font-weight-bold: 600;
824
+ --line-height-tight: 1.2;
825
+ --line-height-normal: 1.5;
826
+ --letter-spacing-tight: -0.01em;
827
+
828
+ /* Spacing */
829
+ --space-0: 0;
830
+ --space-1: 1px;
831
+ --space-2: 2px;
832
+ --space-4: 4px;
833
+ --space-6: 6px;
834
+ --space-8: 8px;
835
+ --space-10: 10px;
836
+ --space-12: 12px;
837
+ --space-16: 16px;
838
+ --space-20: 20px;
839
+ --space-24: 24px;
840
+ --space-32: 32px;
841
+
842
+ /* Border Radius */
843
+ --radius-sm: 6px;
844
+ --radius-base: 8px;
845
+ --radius-md: 10px;
846
+ --radius-lg: 12px;
847
+ --radius-full: 9999px;
848
+
849
+ /* Shadows */
850
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02);
851
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
852
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04), 0 2px 4px -1px rgba(0, 0, 0, 0.02);
853
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04), 0 4px 6px -2px rgba(0, 0, 0, 0.02);
854
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.03);
855
+
856
+ /* Animation */
857
+ --duration-fast: 150ms;
858
+ --duration-normal: 250ms;
859
+ --ease-standard: cubic-bezier(0.16, 1, 0.3, 1);
860
+ }
861
+
862
+ @media (prefers-color-scheme: dark) {
863
+ :root {
864
+ --color-gray-400-rgb: 119, 124, 124;
865
+ --color-teal-300-rgb: 50, 184, 198;
866
+ --color-gray-300-rgb: 167, 169, 169;
867
+ --color-gray-200-rgb: 245, 245, 245;
868
+
869
+ --color-bg-1: rgba(29, 78, 216, 0.15);
870
+ --color-bg-2: rgba(180, 83, 9, 0.15);
871
+ --color-bg-3: rgba(21, 128, 61, 0.15);
872
+ --color-bg-4: rgba(185, 28, 28, 0.15);
873
+ --color-bg-5: rgba(107, 33, 168, 0.15);
874
+ --color-bg-6: rgba(194, 65, 12, 0.15);
875
+ --color-bg-7: rgba(190, 24, 93, 0.15);
876
+ --color-bg-8: rgba(8, 145, 178, 0.15);
877
+
878
+ --color-background: var(--color-charcoal-700);
879
+ --color-surface: var(--color-charcoal-800);
880
+ --color-text: var(--color-gray-200);
881
+ --color-text-secondary: rgba(var(--color-gray-300-rgb), 0.7);
882
+ --color-primary: var(--color-teal-300);
883
+ --color-primary-hover: var(--color-teal-400);
884
+ --color-primary-active: var(--color-teal-800);
885
+ --color-secondary: rgba(var(--color-gray-400-rgb), 0.15);
886
+ --color-secondary-hover: rgba(var(--color-gray-400-rgb), 0.25);
887
+ --color-secondary-active: rgba(var(--color-gray-400-rgb), 0.3);
888
+ --color-border: rgba(var(--color-gray-400-rgb), 0.3);
889
+ --color-error: var(--color-red-400);
890
+ --color-success: var(--color-teal-300);
891
+ --color-warning: var(--color-orange-400);
892
+ --color-info: var(--color-gray-300);
893
+ --color-focus-ring: rgba(var(--color-teal-300-rgb), 0.4);
894
+ --color-btn-primary-text: var(--color-slate-900);
895
+ --color-card-border: rgba(var(--color-gray-400-rgb), 0.2);
896
+ --color-card-border-inner: rgba(var(--color-gray-400-rgb), 0.15);
897
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
898
+ }
899
+ }
900
+
901
+ @font-face {
902
+ font-family: 'FKGroteskNeue';
903
+ src: url('https://r2cdn.perplexity.ai/fonts/FKGroteskNeue.woff2') format('woff2');
904
+ }
905
+
906
+ * {
907
+ margin: 0;
908
+ padding: 0;
909
+ box-sizing: border-box;
910
+ }
911
+
912
+ html, body {
913
+ height: 100%;
914
+ font-family: var(--font-family-base);
915
+ font-size: var(--font-size-base);
916
+ line-height: var(--line-height-normal);
917
+ color: var(--color-text);
918
+ background-color: var(--color-background);
919
+ -webkit-font-smoothing: antialiased;
920
+ }
921
+
922
+ .app-container {
923
+ display: flex;
924
+ height: 100vh;
925
+ overflow: hidden;
926
+ }
927
+
928
+ /* Sidebar Navigation */
929
+ .sidebar {
930
+ width: 260px;
931
+ background-color: var(--color-surface);
932
+ border-right: 1px solid var(--color-border);
933
+ display: flex;
934
+ flex-direction: column;
935
+ overflow-y: auto;
936
+ }
937
+
938
+ .sidebar-header {
939
+ padding: var(--space-24) var(--space-20);
940
+ border-bottom: 1px solid var(--color-border);
941
+ }
942
+
943
+ .sidebar-header h1 {
944
+ font-size: var(--font-size-2xl);
945
+ font-weight: var(--font-weight-bold);
946
+ color: var(--color-text);
947
+ margin-bottom: var(--space-4);
948
+ }
949
+
950
+ .sidebar-header p {
951
+ font-size: var(--font-size-sm);
952
+ color: var(--color-text-secondary);
953
+ }
954
+
955
+ .nav-menu {
956
+ list-style: none;
957
+ padding: var(--space-12);
958
+ }
959
+
960
+ .nav-item {
961
+ display: flex;
962
+ align-items: center;
963
+ padding: var(--space-12) var(--space-16);
964
+ margin-bottom: var(--space-4);
965
+ border-radius: var(--radius-base);
966
+ cursor: pointer;
967
+ transition: all var(--duration-fast) var(--ease-standard);
968
+ color: var(--color-text);
969
+ }
970
+
971
+ .nav-item:hover {
972
+ background-color: var(--color-secondary);
973
+ }
974
+
975
+ .nav-item.active {
976
+ background-color: var(--color-primary);
977
+ color: var(--color-btn-primary-text);
978
+ }
979
+
980
+ .nav-icon {
981
+ font-size: var(--font-size-xl);
982
+ margin-right: var(--space-12);
983
+ }
984
+
985
+ .nav-label {
986
+ font-size: var(--font-size-base);
987
+ font-weight: var(--font-weight-medium);
988
+ }
989
+
990
+ /* Main Content */
991
+ .main-content {
992
+ flex: 1;
993
+ overflow-y: auto;
994
+ padding: var(--space-32);
995
+ }
996
+
997
+ .module {
998
+ max-width: 1400px;
999
+ margin: 0 auto;
1000
+ }
1001
+
1002
+ .module-header {
1003
+ margin-bottom: var(--space-32);
1004
+ }
1005
+
1006
+ .module-header h2 {
1007
+ font-size: var(--font-size-4xl);
1008
+ font-weight: var(--font-weight-bold);
1009
+ margin-bottom: var(--space-8);
1010
+ color: var(--color-text);
1011
+ }
1012
+
1013
+ .module-header p {
1014
+ font-size: var(--font-size-lg);
1015
+ color: var(--color-text-secondary);
1016
+ }
1017
+
1018
+ .content-grid {
1019
+ display: grid;
1020
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
1021
+ gap: var(--space-24);
1022
+ }
1023
+
1024
+ .section {
1025
+ background-color: var(--color-surface);
1026
+ border: 1px solid var(--color-card-border);
1027
+ border-radius: var(--radius-lg);
1028
+ padding: var(--space-24);
1029
+ }
1030
+
1031
+ .section h3 {
1032
+ font-size: var(--font-size-xl);
1033
+ font-weight: var(--font-weight-semibold);
1034
+ margin-bottom: var(--space-16);
1035
+ color: var(--color-text);
1036
+ }
1037
+
1038
+ .section h4 {
1039
+ font-size: var(--font-size-lg);
1040
+ font-weight: var(--font-weight-medium);
1041
+ margin-bottom: var(--space-12);
1042
+ color: var(--color-text);
1043
+ }
1044
+
1045
+ .chart-section {
1046
+ display: flex;
1047
+ flex-direction: column;
1048
+ align-items: center;
1049
+ }
1050
+
1051
+ .chart-section canvas {
1052
+ max-width: 100%;
1053
+ height: auto;
1054
+ }
1055
+
1056
+ .full-width {
1057
+ grid-column: 1 / -1;
1058
+ }
1059
+
1060
+ /* Tables */
1061
+ .table-container {
1062
+ overflow-x: auto;
1063
+ }
1064
+
1065
+ .data-table {
1066
+ width: 100%;
1067
+ border-collapse: collapse;
1068
+ }
1069
+
1070
+ .data-table thead {
1071
+ background-color: var(--color-secondary);
1072
+ }
1073
+
1074
+ .data-table th,
1075
+ .data-table td {
1076
+ padding: var(--space-12);
1077
+ text-align: left;
1078
+ border-bottom: 1px solid var(--color-border);
1079
+ }
1080
+
1081
+ .data-table th {
1082
+ font-weight: var(--font-weight-semibold);
1083
+ font-size: var(--font-size-sm);
1084
+ color: var(--color-text);
1085
+ }
1086
+
1087
+ .data-table td {
1088
+ font-size: var(--font-size-base);
1089
+ color: var(--color-text);
1090
+ }
1091
+
1092
+ .data-table tbody tr:hover {
1093
+ background-color: var(--color-secondary);
1094
+ }
1095
+
1096
+ /* Controls */
1097
+ .controls {
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ gap: var(--space-16);
1101
+ }
1102
+
1103
+ .control-group {
1104
+ display: flex;
1105
+ flex-direction: column;
1106
+ gap: var(--space-8);
1107
+ }
1108
+
1109
+ .control-group label {
1110
+ font-size: var(--font-size-sm);
1111
+ font-weight: var(--font-weight-medium);
1112
+ color: var(--color-text);
1113
+ }
1114
+
1115
+ .control-group input[type="range"] {
1116
+ width: 100%;
1117
+ height: 6px;
1118
+ border-radius: var(--radius-full);
1119
+ background: var(--color-secondary);
1120
+ outline: none;
1121
+ cursor: pointer;
1122
+ }
1123
+
1124
+ .control-group input[type="range"]::-webkit-slider-thumb {
1125
+ -webkit-appearance: none;
1126
+ appearance: none;
1127
+ width: 18px;
1128
+ height: 18px;
1129
+ border-radius: 50%;
1130
+ background: var(--color-primary);
1131
+ cursor: pointer;
1132
+ }
1133
+
1134
+ .control-group input[type="range"]::-moz-range-thumb {
1135
+ width: 18px;
1136
+ height: 18px;
1137
+ border-radius: 50%;
1138
+ background: var(--color-primary);
1139
+ cursor: pointer;
1140
+ border: none;
1141
+ }
1142
+
1143
+ .radio-group {
1144
+ display: flex;
1145
+ gap: var(--space-16);
1146
+ flex-wrap: wrap;
1147
+ }
1148
+
1149
+ .radio-group label {
1150
+ display: flex;
1151
+ align-items: center;
1152
+ gap: var(--space-6);
1153
+ cursor: pointer;
1154
+ font-size: var(--font-size-base);
1155
+ }
1156
+
1157
+ .radio-group input[type="radio"] {
1158
+ cursor: pointer;
1159
+ }
1160
+
1161
+ /* Buttons */
1162
+ .btn {
1163
+ display: inline-flex;
1164
+ align-items: center;
1165
+ justify-content: center;
1166
+ padding: var(--space-10) var(--space-20);
1167
+ border-radius: var(--radius-base);
1168
+ font-size: var(--font-size-base);
1169
+ font-weight: var(--font-weight-medium);
1170
+ cursor: pointer;
1171
+ transition: all var(--duration-normal) var(--ease-standard);
1172
+ border: none;
1173
+ text-decoration: none;
1174
+ }
1175
+
1176
+ .btn--primary {
1177
+ background: var(--color-primary);
1178
+ color: var(--color-btn-primary-text);
1179
+ }
1180
+
1181
+ .btn--primary:hover {
1182
+ background: var(--color-primary-hover);
1183
+ }
1184
+
1185
+ .btn--secondary {
1186
+ background: var(--color-secondary);
1187
+ color: var(--color-text);
1188
+ }
1189
+
1190
+ .btn--secondary:hover {
1191
+ background: var(--color-secondary-hover);
1192
+ }
1193
+
1194
+ /* Info Cards */
1195
+ .info-card {
1196
+ background-color: var(--color-bg-1);
1197
+ border: 1px solid var(--color-border);
1198
+ border-radius: var(--radius-base);
1199
+ padding: var(--space-16);
1200
+ margin-top: var(--space-16);
1201
+ }
1202
+
1203
+ .info-card h4 {
1204
+ font-size: var(--font-size-sm);
1205
+ font-weight: var(--font-weight-medium);
1206
+ color: var(--color-text-secondary);
1207
+ margin-bottom: var(--space-8);
1208
+ }
1209
+
1210
+ .metric-value {
1211
+ font-size: var(--font-size-3xl);
1212
+ font-weight: var(--font-weight-bold);
1213
+ color: var(--color-primary);
1214
+ }
1215
+
1216
+ .metric-detail {
1217
+ font-size: var(--font-size-sm);
1218
+ color: var(--color-text-secondary);
1219
+ margin-top: var(--space-4);
1220
+ }
1221
+
1222
+ /* Explanation Cards */
1223
+ .explanation-card {
1224
+ background-color: var(--color-bg-2);
1225
+ border: 1px solid var(--color-border);
1226
+ border-radius: var(--radius-base);
1227
+ padding: var(--space-20);
1228
+ }
1229
+
1230
+ .explanation-card h4 {
1231
+ font-size: var(--font-size-lg);
1232
+ font-weight: var(--font-weight-semibold);
1233
+ margin-bottom: var(--space-12);
1234
+ }
1235
+
1236
+ .explanation-card p {
1237
+ margin-bottom: var(--space-12);
1238
+ line-height: 1.6;
1239
+ }
1240
+
1241
+ .explanation-card ul {
1242
+ padding-left: var(--space-20);
1243
+ margin-top: var(--space-12);
1244
+ }
1245
+
1246
+ .explanation-card li {
1247
+ margin-bottom: var(--space-8);
1248
+ line-height: 1.6;
1249
+ }
1250
+
1251
+ .formula {
1252
+ font-family: var(--font-family-mono);
1253
+ background-color: var(--color-surface);
1254
+ padding: var(--space-8) var(--space-12);
1255
+ border-radius: var(--radius-sm);
1256
+ display: inline-block;
1257
+ margin: var(--space-8) 0;
1258
+ border: 1px solid var(--color-border);
1259
+ }
1260
+
1261
+ /* Confusion Matrix */
1262
+ .confusion-matrix {
1263
+ display: grid;
1264
+ grid-template-columns: 1fr 1fr;
1265
+ gap: var(--space-12);
1266
+ margin-bottom: var(--space-20);
1267
+ }
1268
+
1269
+ .cm-cell {
1270
+ padding: var(--space-20);
1271
+ border-radius: var(--radius-base);
1272
+ text-align: center;
1273
+ display: flex;
1274
+ flex-direction: column;
1275
+ gap: var(--space-8);
1276
+ }
1277
+
1278
+ .cm-label {
1279
+ font-size: var(--font-size-sm);
1280
+ font-weight: var(--font-weight-medium);
1281
+ color: var(--color-text-secondary);
1282
+ }
1283
+
1284
+ .cm-value {
1285
+ font-size: var(--font-size-3xl);
1286
+ font-weight: var(--font-weight-bold);
1287
+ }
1288
+
1289
+ .cm-tn {
1290
+ background-color: var(--color-bg-3);
1291
+ border: 2px solid var(--color-success);
1292
+ }
1293
+
1294
+ .cm-tn .cm-value {
1295
+ color: var(--color-success);
1296
+ }
1297
+
1298
+ .cm-fp {
1299
+ background-color: var(--color-bg-4);
1300
+ border: 2px solid var(--color-error);
1301
+ }
1302
+
1303
+ .cm-fp .cm-value {
1304
+ color: var(--color-error);
1305
+ }
1306
+
1307
+ .cm-fn {
1308
+ background-color: var(--color-bg-4);
1309
+ border: 2px solid var(--color-error);
1310
+ }
1311
+
1312
+ .cm-fn .cm-value {
1313
+ color: var(--color-error);
1314
+ }
1315
+
1316
+ .cm-tp {
1317
+ background-color: var(--color-bg-3);
1318
+ border: 2px solid var(--color-success);
1319
+ }
1320
+
1321
+ .cm-tp .cm-value {
1322
+ color: var(--color-success);
1323
+ }
1324
+
1325
+ /* Metrics Panel */
1326
+ .metrics-panel {
1327
+ display: flex;
1328
+ gap: var(--space-20);
1329
+ flex-wrap: wrap;
1330
+ margin-top: var(--space-16);
1331
+ }
1332
+
1333
+ .metric-item {
1334
+ flex: 1;
1335
+ min-width: 150px;
1336
+ padding: var(--space-12);
1337
+ background-color: var(--color-secondary);
1338
+ border-radius: var(--radius-base);
1339
+ display: flex;
1340
+ flex-direction: column;
1341
+ gap: var(--space-4);
1342
+ }
1343
+
1344
+ .metric-label {
1345
+ font-size: var(--font-size-sm);
1346
+ color: var(--color-text-secondary);
1347
+ }
1348
+
1349
+ .metric-val {
1350
+ font-size: var(--font-size-xl);
1351
+ font-weight: var(--font-weight-bold);
1352
+ color: var(--color-text);
1353
+ }
1354
+
1355
+ /* Three Charts Layout */
1356
+ .three-charts {
1357
+ display: grid;
1358
+ grid-template-columns: repeat(3, 1fr);
1359
+ gap: var(--space-20);
1360
+ width: 100%;
1361
+ }
1362
+
1363
+ .chart-container {
1364
+ display: flex;
1365
+ flex-direction: column;
1366
+ align-items: center;
1367
+ gap: var(--space-12);
1368
+ }
1369
+
1370
+ .chart-container h4 {
1371
+ font-size: var(--font-size-base);
1372
+ font-weight: var(--font-weight-semibold);
1373
+ text-align: center;
1374
+ }
1375
+
1376
+ .chart-desc {
1377
+ font-size: var(--font-size-sm);
1378
+ color: var(--color-text-secondary);
1379
+ text-align: center;
1380
+ margin-top: var(--space-8);
1381
+ }
1382
+
1383
+ /* Calculation Panel */
1384
+ .calculation-panel {
1385
+ display: flex;
1386
+ flex-direction: column;
1387
+ gap: var(--space-12);
1388
+ }
1389
+
1390
+ .calc-item {
1391
+ display: flex;
1392
+ justify-content: space-between;
1393
+ align-items: center;
1394
+ padding: var(--space-12);
1395
+ background-color: var(--color-secondary);
1396
+ border-radius: var(--radius-base);
1397
+ }
1398
+
1399
+ .calc-item strong {
1400
+ font-weight: var(--font-weight-medium);
1401
+ }
1402
+
1403
+ .calc-item span {
1404
+ font-family: var(--font-family-mono);
1405
+ color: var(--color-primary);
1406
+ font-weight: var(--font-weight-semibold);
1407
+ }
1408
+
1409
+ /* Responsive adjustments */
1410
+ @media (max-width: 1200px) {
1411
+ .content-grid {
1412
+ grid-template-columns: 1fr;
1413
+ }
1414
+
1415
+ .three-charts {
1416
+ grid-template-columns: 1fr;
1417
+ }
1418
+ }
1419
+
1420
+ /* Pipeline Flow */
1421
+ .pipeline-flow {
1422
+ display: grid;
1423
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1424
+ gap: var(--space-16);
1425
+ }
1426
+
1427
+ .pipeline-stage {
1428
+ background: var(--color-bg-1);
1429
+ border: 1px solid var(--color-border);
1430
+ border-radius: var(--radius-base);
1431
+ padding: var(--space-16);
1432
+ }
1433
+
1434
+ .pipeline-stage h4 {
1435
+ font-size: var(--font-size-base);
1436
+ margin-bottom: var(--space-12);
1437
+ color: var(--color-primary);
1438
+ }
1439
+
1440
+ /* Workflow Container */
1441
+ .workflow-container {
1442
+ display: flex;
1443
+ align-items: center;
1444
+ justify-content: space-between;
1445
+ flex-wrap: wrap;
1446
+ gap: var(--space-16);
1447
+ padding: var(--space-24);
1448
+ }
1449
+
1450
+ .workflow-stage {
1451
+ flex: 1;
1452
+ min-width: 140px;
1453
+ background: var(--color-bg-1);
1454
+ border: 2px solid var(--color-border);
1455
+ border-radius: var(--radius-lg);
1456
+ padding: var(--space-20);
1457
+ text-align: center;
1458
+ transition: all var(--duration-normal) var(--ease-standard);
1459
+ cursor: pointer;
1460
+ }
1461
+
1462
+ .workflow-stage:hover {
1463
+ transform: translateY(-4px);
1464
+ box-shadow: var(--shadow-lg);
1465
+ border-color: var(--color-primary);
1466
+ }
1467
+
1468
+ .workflow-stage.active {
1469
+ background: var(--color-primary);
1470
+ color: var(--color-btn-primary-text);
1471
+ border-color: var(--color-primary);
1472
+ }
1473
+
1474
+ .stage-icon {
1475
+ font-size: 32px;
1476
+ margin-bottom: var(--space-8);
1477
+ }
1478
+
1479
+ .workflow-stage h4 {
1480
+ font-size: var(--font-size-base);
1481
+ margin-bottom: var(--space-8);
1482
+ }
1483
+
1484
+ .workflow-stage p {
1485
+ font-size: var(--font-size-sm);
1486
+ color: var(--color-text-secondary);
1487
+ margin-bottom: var(--space-12);
1488
+ }
1489
+
1490
+ .workflow-stage.active p {
1491
+ color: var(--color-btn-primary-text);
1492
+ opacity: 0.9;
1493
+ }
1494
+
1495
+ .workflow-arrow {
1496
+ font-size: 24px;
1497
+ color: var(--color-primary);
1498
+ font-weight: bold;
1499
+ }
1500
+
1501
+ .stage-btn {
1502
+ font-size: var(--font-size-sm);
1503
+ padding: var(--space-6) var(--space-12);
1504
+ }
1505
+
1506
+ @media (max-width: 1200px) {
1507
+ .workflow-container {
1508
+ flex-direction: column;
1509
+ }
1510
+
1511
+ .workflow-arrow {
1512
+ transform: rotate(90deg);
1513
+ }
1514
+ }
1515
+
1516
+ @media (max-width: 768px) {
1517
+ .sidebar {
1518
+ width: 80px;
1519
+ }
1520
+
1521
+ .nav-label {
1522
+ display: none;
1523
+ }
1524
+
1525
+ .sidebar-header h1 {
1526
+ font-size: var(--font-size-lg);
1527
+ }
1528
+
1529
+ .sidebar-header p {
1530
+ display: none;
1531
+ }
1532
+ }