magick-feature-flags 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/views/layouts/application.html.erb +849 -361
- data/app/views/magick/adminui/features/edit.html.erb +174 -71
- data/app/views/magick/adminui/features/index.html.erb +47 -48
- data/app/views/magick/adminui/features/show.html.erb +138 -109
- data/app/views/magick/adminui/stats/show.html.erb +48 -38
- data/lib/magick/version.rb +1 -1
- metadata +1 -1
|
@@ -4,561 +4,880 @@
|
|
|
4
4
|
<title>Magick Feature Flags</title>
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<style>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
:root {
|
|
8
|
+
--color-bg: #f8fafc;
|
|
9
|
+
--color-surface: #ffffff;
|
|
10
|
+
--color-nav: #0f172a;
|
|
11
|
+
--color-nav-text: #e2e8f0;
|
|
12
|
+
--color-text: #1e293b;
|
|
13
|
+
--color-text-secondary: #64748b;
|
|
14
|
+
--color-text-muted: #94a3b8;
|
|
15
|
+
--color-border: #e2e8f0;
|
|
16
|
+
--color-border-subtle: #f1f5f9;
|
|
17
|
+
--color-primary: #6366f1;
|
|
18
|
+
--color-primary-hover: #4f46e5;
|
|
19
|
+
--color-primary-light: #eef2ff;
|
|
20
|
+
--color-primary-text: #4338ca;
|
|
21
|
+
--color-success: #10b981;
|
|
22
|
+
--color-success-hover: #059669;
|
|
23
|
+
--color-success-light: #ecfdf5;
|
|
24
|
+
--color-success-text: #065f46;
|
|
25
|
+
--color-danger: #f43f5e;
|
|
26
|
+
--color-danger-hover: #e11d48;
|
|
27
|
+
--color-danger-light: #fff1f2;
|
|
28
|
+
--color-danger-text: #9f1239;
|
|
29
|
+
--color-warning: #f59e0b;
|
|
30
|
+
--color-warning-light: #fffbeb;
|
|
31
|
+
--color-warning-text: #92400e;
|
|
32
|
+
--color-info-light: #eff6ff;
|
|
33
|
+
--color-info-text: #1e40af;
|
|
34
|
+
--font-stack: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Inter', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
35
|
+
--font-mono: 'SF Mono', SFMono-Regular, ui-monospace, 'DejaVu Sans Mono', Menlo, Consolas, monospace;
|
|
36
|
+
--radius-sm: 6px;
|
|
37
|
+
--radius-md: 8px;
|
|
38
|
+
--radius-lg: 12px;
|
|
39
|
+
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
|
11
40
|
}
|
|
41
|
+
|
|
42
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
43
|
+
|
|
12
44
|
body {
|
|
13
|
-
font-family: -
|
|
14
|
-
background:
|
|
15
|
-
color:
|
|
16
|
-
line-height: 1.
|
|
45
|
+
font-family: var(--font-stack);
|
|
46
|
+
background: var(--color-bg);
|
|
47
|
+
color: var(--color-text);
|
|
48
|
+
line-height: 1.5;
|
|
49
|
+
font-size: 14px;
|
|
50
|
+
-webkit-font-smoothing: antialiased;
|
|
51
|
+
-moz-osx-font-smoothing: grayscale;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.nav {
|
|
55
|
+
background: var(--color-nav);
|
|
56
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
57
|
+
position: sticky;
|
|
58
|
+
top: 0;
|
|
59
|
+
z-index: 100;
|
|
60
|
+
}
|
|
61
|
+
.nav-inner {
|
|
62
|
+
max-width: 1200px;
|
|
63
|
+
margin: 0 auto;
|
|
64
|
+
padding: 0 24px;
|
|
65
|
+
height: 56px;
|
|
66
|
+
display: flex;
|
|
67
|
+
align-items: center;
|
|
17
68
|
}
|
|
69
|
+
.nav-brand {
|
|
70
|
+
font-size: 15px;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
color: #ffffff;
|
|
73
|
+
text-decoration: none;
|
|
74
|
+
letter-spacing: -0.01em;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 8px;
|
|
78
|
+
}
|
|
79
|
+
.nav-brand:hover { color: #ffffff; }
|
|
80
|
+
.nav-brand-icon {
|
|
81
|
+
width: 28px;
|
|
82
|
+
height: 28px;
|
|
83
|
+
background: var(--color-primary);
|
|
84
|
+
border-radius: 6px;
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
justify-content: center;
|
|
88
|
+
font-weight: 700;
|
|
89
|
+
font-size: 14px;
|
|
90
|
+
color: #fff;
|
|
91
|
+
}
|
|
92
|
+
|
|
18
93
|
.container {
|
|
19
94
|
max-width: 1200px;
|
|
20
95
|
margin: 0 auto;
|
|
21
|
-
padding:
|
|
96
|
+
padding: 24px;
|
|
22
97
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
padding:
|
|
27
|
-
|
|
98
|
+
|
|
99
|
+
/* Flash messages */
|
|
100
|
+
.flash-banner {
|
|
101
|
+
padding: 12px 24px;
|
|
102
|
+
font-size: 13px;
|
|
103
|
+
font-weight: 500;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: space-between;
|
|
107
|
+
animation: flash-in 0.3s ease-out;
|
|
28
108
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
109
|
+
.flash-banner-success {
|
|
110
|
+
background: var(--color-success-light);
|
|
111
|
+
color: var(--color-success-text);
|
|
112
|
+
border-bottom: 1px solid #a7f3d0;
|
|
33
113
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
color:
|
|
37
|
-
|
|
114
|
+
.flash-banner-danger {
|
|
115
|
+
background: var(--color-danger-light);
|
|
116
|
+
color: var(--color-danger-text);
|
|
117
|
+
border-bottom: 1px solid #fecdd3;
|
|
118
|
+
}
|
|
119
|
+
.flash-dismiss {
|
|
120
|
+
background: none;
|
|
121
|
+
border: none;
|
|
122
|
+
cursor: pointer;
|
|
123
|
+
font-size: 18px;
|
|
124
|
+
line-height: 1;
|
|
125
|
+
opacity: 0.6;
|
|
126
|
+
color: inherit;
|
|
127
|
+
padding: 0 4px;
|
|
38
128
|
}
|
|
39
|
-
|
|
40
|
-
|
|
129
|
+
.flash-dismiss:hover { opacity: 1; }
|
|
130
|
+
@keyframes flash-in {
|
|
131
|
+
from { opacity: 0; transform: translateY(-8px); }
|
|
132
|
+
to { opacity: 1; transform: translateY(0); }
|
|
41
133
|
}
|
|
134
|
+
|
|
135
|
+
/* Cards */
|
|
42
136
|
.card {
|
|
43
|
-
background:
|
|
44
|
-
border
|
|
45
|
-
|
|
46
|
-
padding: 24px;
|
|
137
|
+
background: var(--color-surface);
|
|
138
|
+
border: 1px solid var(--color-border);
|
|
139
|
+
border-radius: var(--radius-lg);
|
|
47
140
|
margin-bottom: 20px;
|
|
48
141
|
}
|
|
142
|
+
.card-body { padding: 24px; }
|
|
49
143
|
.card-header {
|
|
50
144
|
display: flex;
|
|
51
145
|
justify-content: space-between;
|
|
52
146
|
align-items: center;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
border-bottom: 1px solid #e0e0e0;
|
|
147
|
+
padding: 20px 24px;
|
|
148
|
+
border-bottom: 1px solid var(--color-border);
|
|
56
149
|
}
|
|
57
150
|
.card-header h2 {
|
|
58
|
-
font-size:
|
|
151
|
+
font-size: 15px;
|
|
59
152
|
font-weight: 600;
|
|
60
|
-
color:
|
|
61
|
-
}
|
|
62
|
-
table {
|
|
63
|
-
width: 100%;
|
|
64
|
-
border-collapse: collapse;
|
|
65
|
-
}
|
|
66
|
-
th, td {
|
|
67
|
-
padding: 12px;
|
|
68
|
-
text-align: left;
|
|
69
|
-
border-bottom: 1px solid #e0e0e0;
|
|
70
|
-
}
|
|
71
|
-
th {
|
|
72
|
-
font-weight: 600;
|
|
73
|
-
color: #666;
|
|
74
|
-
font-size: 14px;
|
|
75
|
-
text-transform: uppercase;
|
|
76
|
-
letter-spacing: 0.5px;
|
|
77
|
-
}
|
|
78
|
-
tr:hover {
|
|
79
|
-
background: #f9f9f9;
|
|
153
|
+
color: var(--color-text);
|
|
80
154
|
}
|
|
155
|
+
|
|
156
|
+
/* Buttons */
|
|
81
157
|
.btn {
|
|
82
|
-
display: inline-
|
|
83
|
-
|
|
84
|
-
|
|
158
|
+
display: inline-flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
justify-content: center;
|
|
161
|
+
gap: 6px;
|
|
162
|
+
padding: 8px 14px;
|
|
163
|
+
border-radius: var(--radius-sm);
|
|
85
164
|
text-decoration: none;
|
|
86
|
-
font-size:
|
|
165
|
+
font-size: 13px;
|
|
87
166
|
font-weight: 500;
|
|
88
167
|
cursor: pointer;
|
|
89
|
-
border:
|
|
90
|
-
transition: all 0.
|
|
168
|
+
border: 1px solid transparent;
|
|
169
|
+
transition: all 0.15s ease;
|
|
170
|
+
line-height: 1.4;
|
|
171
|
+
white-space: nowrap;
|
|
91
172
|
}
|
|
92
173
|
.btn-primary {
|
|
93
|
-
background:
|
|
174
|
+
background: var(--color-primary);
|
|
94
175
|
color: #fff;
|
|
176
|
+
border-color: var(--color-primary);
|
|
95
177
|
}
|
|
96
178
|
.btn-primary:hover {
|
|
97
|
-
background:
|
|
179
|
+
background: var(--color-primary-hover);
|
|
180
|
+
border-color: var(--color-primary-hover);
|
|
98
181
|
}
|
|
99
182
|
.btn-success {
|
|
100
|
-
background:
|
|
183
|
+
background: var(--color-success);
|
|
101
184
|
color: #fff;
|
|
185
|
+
border-color: var(--color-success);
|
|
102
186
|
}
|
|
103
187
|
.btn-success:hover {
|
|
104
|
-
background:
|
|
188
|
+
background: var(--color-success-hover);
|
|
189
|
+
border-color: var(--color-success-hover);
|
|
105
190
|
}
|
|
106
191
|
.btn-danger {
|
|
107
|
-
background:
|
|
192
|
+
background: var(--color-danger);
|
|
108
193
|
color: #fff;
|
|
194
|
+
border-color: var(--color-danger);
|
|
109
195
|
}
|
|
110
196
|
.btn-danger:hover {
|
|
111
|
-
background:
|
|
197
|
+
background: var(--color-danger-hover);
|
|
198
|
+
border-color: var(--color-danger-hover);
|
|
112
199
|
}
|
|
113
200
|
.btn-secondary {
|
|
114
|
-
background:
|
|
115
|
-
color:
|
|
201
|
+
background: var(--color-surface);
|
|
202
|
+
color: var(--color-text);
|
|
203
|
+
border-color: var(--color-border);
|
|
116
204
|
}
|
|
117
205
|
.btn-secondary:hover {
|
|
118
|
-
background:
|
|
206
|
+
background: var(--color-bg);
|
|
207
|
+
border-color: #cbd5e1;
|
|
208
|
+
}
|
|
209
|
+
.btn-ghost {
|
|
210
|
+
background: transparent;
|
|
211
|
+
color: var(--color-text-secondary);
|
|
212
|
+
border-color: transparent;
|
|
213
|
+
padding: 6px 10px;
|
|
214
|
+
}
|
|
215
|
+
.btn-ghost:hover {
|
|
216
|
+
background: var(--color-bg);
|
|
217
|
+
color: var(--color-text);
|
|
119
218
|
}
|
|
120
219
|
.btn-sm {
|
|
121
|
-
padding:
|
|
122
|
-
font-size:
|
|
123
|
-
min-width: 80px;
|
|
124
|
-
text-align: center;
|
|
220
|
+
padding: 6px 12px;
|
|
221
|
+
font-size: 13px;
|
|
125
222
|
}
|
|
126
223
|
.btn-group {
|
|
127
224
|
display: flex;
|
|
128
225
|
gap: 8px;
|
|
129
226
|
flex-wrap: wrap;
|
|
227
|
+
align-items: center;
|
|
130
228
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
form.button_to {
|
|
135
|
-
display: inline-block;
|
|
136
|
-
margin: 0;
|
|
137
|
-
}
|
|
229
|
+
|
|
230
|
+
form.button_to { display: inline-block; margin: 0; }
|
|
138
231
|
form.button_to input[type="submit"] {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
232
|
+
display: inline-flex;
|
|
233
|
+
align-items: center;
|
|
234
|
+
justify-content: center;
|
|
235
|
+
padding: 8px 14px;
|
|
236
|
+
border-radius: var(--radius-sm);
|
|
237
|
+
font-size: 13px;
|
|
238
|
+
font-weight: 500;
|
|
239
|
+
cursor: pointer;
|
|
240
|
+
border: 1px solid transparent;
|
|
241
|
+
transition: all 0.15s ease;
|
|
242
|
+
line-height: 1.4;
|
|
143
243
|
}
|
|
244
|
+
|
|
245
|
+
/* Table */
|
|
246
|
+
table { width: 100%; border-collapse: collapse; }
|
|
247
|
+
th {
|
|
248
|
+
padding: 10px 16px;
|
|
249
|
+
text-align: left;
|
|
250
|
+
font-weight: 500;
|
|
251
|
+
color: var(--color-text-secondary);
|
|
252
|
+
font-size: 12px;
|
|
253
|
+
text-transform: uppercase;
|
|
254
|
+
letter-spacing: 0.05em;
|
|
255
|
+
border-bottom: 1px solid var(--color-border);
|
|
256
|
+
background: var(--color-bg);
|
|
257
|
+
}
|
|
258
|
+
td {
|
|
259
|
+
padding: 14px 16px;
|
|
260
|
+
border-bottom: 1px solid var(--color-border-subtle);
|
|
261
|
+
vertical-align: middle;
|
|
262
|
+
}
|
|
263
|
+
tbody tr { transition: background-color 0.1s ease; }
|
|
264
|
+
tbody tr:hover { background: var(--color-bg); }
|
|
265
|
+
tbody tr:last-child td { border-bottom: none; }
|
|
266
|
+
|
|
267
|
+
/* Badges / Pills */
|
|
144
268
|
.badge {
|
|
145
|
-
display: inline-
|
|
146
|
-
|
|
147
|
-
|
|
269
|
+
display: inline-flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
padding: 2px 8px;
|
|
272
|
+
border-radius: 100px;
|
|
148
273
|
font-size: 12px;
|
|
149
274
|
font-weight: 500;
|
|
275
|
+
line-height: 1.6;
|
|
150
276
|
}
|
|
151
277
|
.badge-success {
|
|
152
|
-
background:
|
|
153
|
-
color:
|
|
278
|
+
background: var(--color-success-light);
|
|
279
|
+
color: var(--color-success-text);
|
|
154
280
|
}
|
|
155
281
|
.badge-warning {
|
|
156
|
-
background:
|
|
157
|
-
color:
|
|
282
|
+
background: var(--color-warning-light);
|
|
283
|
+
color: var(--color-warning-text);
|
|
158
284
|
}
|
|
159
285
|
.badge-danger {
|
|
160
|
-
background:
|
|
161
|
-
color:
|
|
286
|
+
background: var(--color-danger-light);
|
|
287
|
+
color: var(--color-danger-text);
|
|
162
288
|
}
|
|
163
289
|
.badge-info {
|
|
164
|
-
background:
|
|
165
|
-
color:
|
|
290
|
+
background: var(--color-primary-light);
|
|
291
|
+
color: var(--color-primary-text);
|
|
166
292
|
}
|
|
167
|
-
.
|
|
168
|
-
|
|
293
|
+
.badge-neutral {
|
|
294
|
+
background: var(--color-bg);
|
|
295
|
+
color: var(--color-text-secondary);
|
|
296
|
+
border: 1px solid var(--color-border);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Status dot */
|
|
300
|
+
.status-indicator {
|
|
301
|
+
display: inline-flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
gap: 6px;
|
|
304
|
+
font-size: 13px;
|
|
305
|
+
}
|
|
306
|
+
.status-dot {
|
|
307
|
+
width: 8px;
|
|
308
|
+
height: 8px;
|
|
309
|
+
border-radius: 50%;
|
|
310
|
+
flex-shrink: 0;
|
|
311
|
+
}
|
|
312
|
+
.status-dot-green { background: var(--color-success); }
|
|
313
|
+
.status-dot-yellow { background: var(--color-warning); }
|
|
314
|
+
.status-dot-red { background: var(--color-danger); }
|
|
315
|
+
|
|
316
|
+
/* Toggle indicator */
|
|
317
|
+
.toggle-indicator {
|
|
318
|
+
display: inline-flex;
|
|
319
|
+
align-items: center;
|
|
320
|
+
padding: 2px 10px;
|
|
321
|
+
border-radius: 100px;
|
|
322
|
+
font-size: 12px;
|
|
323
|
+
font-weight: 600;
|
|
324
|
+
letter-spacing: 0.02em;
|
|
325
|
+
}
|
|
326
|
+
.toggle-on {
|
|
327
|
+
background: var(--color-success);
|
|
328
|
+
color: #fff;
|
|
329
|
+
}
|
|
330
|
+
.toggle-off {
|
|
331
|
+
background: #e2e8f0;
|
|
332
|
+
color: var(--color-text-secondary);
|
|
333
|
+
}
|
|
334
|
+
.toggle-partial {
|
|
335
|
+
background: var(--color-warning);
|
|
336
|
+
color: #fff;
|
|
169
337
|
}
|
|
170
|
-
|
|
338
|
+
|
|
339
|
+
/* Forms */
|
|
340
|
+
.form-group { margin-bottom: 24px; }
|
|
341
|
+
.form-label {
|
|
171
342
|
display: block;
|
|
172
|
-
margin-bottom:
|
|
343
|
+
margin-bottom: 6px;
|
|
173
344
|
font-weight: 500;
|
|
174
|
-
|
|
345
|
+
font-size: 13px;
|
|
346
|
+
color: var(--color-text);
|
|
347
|
+
}
|
|
348
|
+
.form-hint {
|
|
349
|
+
font-size: 12px;
|
|
350
|
+
color: var(--color-text-muted);
|
|
351
|
+
margin-top: 6px;
|
|
175
352
|
}
|
|
176
353
|
input[type="text"],
|
|
177
354
|
input[type="number"],
|
|
178
355
|
select {
|
|
179
356
|
width: 100%;
|
|
180
|
-
padding:
|
|
181
|
-
border: 1px solid
|
|
182
|
-
border-radius:
|
|
357
|
+
padding: 8px 12px;
|
|
358
|
+
border: 1px solid var(--color-border);
|
|
359
|
+
border-radius: var(--radius-sm);
|
|
183
360
|
font-size: 14px;
|
|
361
|
+
font-family: var(--font-stack);
|
|
362
|
+
color: var(--color-text);
|
|
363
|
+
background: var(--color-surface);
|
|
364
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
365
|
+
outline: none;
|
|
184
366
|
}
|
|
185
|
-
input[type="
|
|
186
|
-
|
|
367
|
+
input[type="text"]:focus,
|
|
368
|
+
input[type="number"]:focus,
|
|
369
|
+
select:focus {
|
|
370
|
+
border-color: var(--color-primary);
|
|
371
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
187
372
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
373
|
+
input[type="text"]:disabled,
|
|
374
|
+
input[type="number"]:disabled {
|
|
375
|
+
background: var(--color-bg);
|
|
376
|
+
color: var(--color-text-secondary);
|
|
377
|
+
cursor: not-allowed;
|
|
192
378
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
379
|
+
input[type="checkbox"] { width: auto; }
|
|
380
|
+
|
|
381
|
+
code {
|
|
382
|
+
font-family: var(--font-mono);
|
|
383
|
+
font-size: 12px;
|
|
384
|
+
background: var(--color-bg);
|
|
385
|
+
border: 1px solid var(--color-border);
|
|
386
|
+
padding: 2px 6px;
|
|
387
|
+
border-radius: 4px;
|
|
388
|
+
color: var(--color-text);
|
|
197
389
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
390
|
+
|
|
391
|
+
/* Feature detail grid */
|
|
392
|
+
.detail-row {
|
|
393
|
+
display: flex;
|
|
394
|
+
gap: 12px;
|
|
395
|
+
flex-wrap: wrap;
|
|
396
|
+
margin-bottom: 20px;
|
|
202
397
|
}
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
398
|
+
.detail-chip {
|
|
399
|
+
display: flex;
|
|
400
|
+
flex-direction: column;
|
|
401
|
+
gap: 4px;
|
|
402
|
+
padding: 12px 16px;
|
|
403
|
+
background: var(--color-bg);
|
|
404
|
+
border: 1px solid var(--color-border);
|
|
405
|
+
border-radius: var(--radius-md);
|
|
406
|
+
min-width: 120px;
|
|
207
407
|
}
|
|
208
|
-
.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
408
|
+
.detail-chip-label {
|
|
409
|
+
font-size: 11px;
|
|
410
|
+
font-weight: 500;
|
|
411
|
+
color: var(--color-text-muted);
|
|
412
|
+
text-transform: uppercase;
|
|
413
|
+
letter-spacing: 0.05em;
|
|
213
414
|
}
|
|
214
|
-
.detail-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
415
|
+
.detail-chip-value {
|
|
416
|
+
font-size: 14px;
|
|
417
|
+
font-weight: 500;
|
|
418
|
+
color: var(--color-text);
|
|
218
419
|
}
|
|
219
|
-
|
|
420
|
+
|
|
421
|
+
/* Targeting rules */
|
|
422
|
+
.targeting-section { margin-top: 4px; }
|
|
423
|
+
.targeting-section h4 {
|
|
220
424
|
font-size: 12px;
|
|
221
|
-
|
|
425
|
+
font-weight: 600;
|
|
222
426
|
text-transform: uppercase;
|
|
223
|
-
letter-spacing: 0.
|
|
224
|
-
margin-bottom:
|
|
427
|
+
letter-spacing: 0.05em;
|
|
428
|
+
margin-bottom: 12px;
|
|
429
|
+
color: var(--color-text-secondary);
|
|
430
|
+
}
|
|
431
|
+
.targeting-section-inclusion h4 { color: var(--color-primary-text); }
|
|
432
|
+
.targeting-section-exclusion h4 { color: var(--color-danger-text); }
|
|
433
|
+
.targeting-rule {
|
|
434
|
+
display: flex;
|
|
435
|
+
align-items: baseline;
|
|
436
|
+
gap: 8px;
|
|
437
|
+
padding: 10px 14px;
|
|
438
|
+
background: var(--color-bg);
|
|
439
|
+
border: 1px solid var(--color-border);
|
|
440
|
+
border-radius: var(--radius-sm);
|
|
441
|
+
margin-bottom: 8px;
|
|
442
|
+
}
|
|
443
|
+
.targeting-rule-exclusion {
|
|
444
|
+
background: var(--color-danger-light);
|
|
445
|
+
border-color: #fecdd3;
|
|
225
446
|
}
|
|
226
|
-
.
|
|
227
|
-
font-size:
|
|
447
|
+
.targeting-rule-label {
|
|
448
|
+
font-size: 13px;
|
|
228
449
|
font-weight: 500;
|
|
229
|
-
color:
|
|
450
|
+
color: var(--color-text-secondary);
|
|
451
|
+
white-space: nowrap;
|
|
230
452
|
}
|
|
231
|
-
.targeting-
|
|
232
|
-
|
|
453
|
+
.targeting-rule-values {
|
|
454
|
+
display: flex;
|
|
455
|
+
flex-wrap: wrap;
|
|
456
|
+
gap: 4px;
|
|
233
457
|
}
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
|
|
458
|
+
.pill {
|
|
459
|
+
display: inline-flex;
|
|
460
|
+
align-items: center;
|
|
461
|
+
padding: 2px 10px;
|
|
462
|
+
border-radius: 100px;
|
|
463
|
+
font-size: 12px;
|
|
464
|
+
font-weight: 500;
|
|
237
465
|
}
|
|
238
|
-
.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
466
|
+
.pill-inclusion {
|
|
467
|
+
background: var(--color-primary-light);
|
|
468
|
+
color: var(--color-primary-text);
|
|
469
|
+
}
|
|
470
|
+
.pill-exclusion {
|
|
471
|
+
background: var(--color-danger-light);
|
|
472
|
+
color: var(--color-danger-text);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/* Breadcrumb */
|
|
476
|
+
.breadcrumb {
|
|
477
|
+
display: flex;
|
|
478
|
+
align-items: center;
|
|
479
|
+
gap: 8px;
|
|
480
|
+
margin-bottom: 20px;
|
|
481
|
+
font-size: 13px;
|
|
482
|
+
}
|
|
483
|
+
.breadcrumb a {
|
|
484
|
+
color: var(--color-text-secondary);
|
|
485
|
+
text-decoration: none;
|
|
486
|
+
}
|
|
487
|
+
.breadcrumb a:hover { color: var(--color-primary); }
|
|
488
|
+
.breadcrumb-sep { color: var(--color-text-muted); }
|
|
489
|
+
.breadcrumb-current {
|
|
490
|
+
color: var(--color-text);
|
|
491
|
+
font-weight: 500;
|
|
244
492
|
}
|
|
493
|
+
|
|
494
|
+
/* Stats */
|
|
245
495
|
.stats-grid {
|
|
246
496
|
display: grid;
|
|
247
497
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
248
|
-
gap:
|
|
249
|
-
margin-top: 20px;
|
|
498
|
+
gap: 16px;
|
|
250
499
|
}
|
|
251
500
|
.stat-card {
|
|
252
|
-
background:
|
|
501
|
+
background: var(--color-surface);
|
|
502
|
+
border: 1px solid var(--color-border);
|
|
503
|
+
border-radius: var(--radius-md);
|
|
253
504
|
padding: 20px;
|
|
254
|
-
border-radius: 4px;
|
|
255
|
-
text-align: center;
|
|
256
505
|
}
|
|
257
506
|
.stat-value {
|
|
258
|
-
font-size:
|
|
259
|
-
font-weight:
|
|
260
|
-
color:
|
|
261
|
-
|
|
507
|
+
font-size: 28px;
|
|
508
|
+
font-weight: 700;
|
|
509
|
+
color: var(--color-text);
|
|
510
|
+
letter-spacing: -0.02em;
|
|
511
|
+
margin-bottom: 4px;
|
|
262
512
|
}
|
|
263
513
|
.stat-label {
|
|
264
|
-
font-size:
|
|
265
|
-
|
|
514
|
+
font-size: 12px;
|
|
515
|
+
font-weight: 500;
|
|
516
|
+
color: var(--color-text-muted);
|
|
266
517
|
text-transform: uppercase;
|
|
267
|
-
letter-spacing: 0.
|
|
518
|
+
letter-spacing: 0.05em;
|
|
268
519
|
}
|
|
520
|
+
|
|
521
|
+
/* Empty state */
|
|
269
522
|
.empty-state {
|
|
270
523
|
text-align: center;
|
|
271
524
|
padding: 60px 20px;
|
|
272
|
-
color:
|
|
525
|
+
color: var(--color-text-muted);
|
|
273
526
|
}
|
|
274
527
|
.empty-state h3 {
|
|
275
|
-
margin-bottom:
|
|
276
|
-
color:
|
|
528
|
+
margin-bottom: 8px;
|
|
529
|
+
color: var(--color-text-secondary);
|
|
530
|
+
font-size: 15px;
|
|
531
|
+
font-weight: 600;
|
|
532
|
+
}
|
|
533
|
+
.empty-state p { font-size: 13px; }
|
|
534
|
+
|
|
535
|
+
/* Section divider */
|
|
536
|
+
.section-divider {
|
|
537
|
+
border: none;
|
|
538
|
+
border-top: 1px solid var(--color-border);
|
|
539
|
+
margin: 28px 0;
|
|
540
|
+
}
|
|
541
|
+
.section-title {
|
|
542
|
+
font-size: 15px;
|
|
543
|
+
font-weight: 600;
|
|
544
|
+
color: var(--color-text);
|
|
545
|
+
margin-bottom: 20px;
|
|
546
|
+
}
|
|
547
|
+
.section-title-danger {
|
|
548
|
+
color: var(--color-danger-text);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/* Alert */
|
|
552
|
+
.alert {
|
|
553
|
+
padding: 12px 16px;
|
|
554
|
+
border-radius: var(--radius-sm);
|
|
555
|
+
margin-bottom: 16px;
|
|
556
|
+
font-size: 13px;
|
|
557
|
+
}
|
|
558
|
+
.alert-info {
|
|
559
|
+
background: var(--color-info-light);
|
|
560
|
+
color: var(--color-info-text);
|
|
561
|
+
border: 1px solid #bfdbfe;
|
|
562
|
+
}
|
|
563
|
+
.alert code {
|
|
564
|
+
background: rgba(255,255,255,0.5);
|
|
565
|
+
border-color: rgba(0,0,0,0.08);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/* Toggle switch (CSS-only) */
|
|
569
|
+
.toggle-switch {
|
|
570
|
+
position: relative;
|
|
571
|
+
display: inline-flex;
|
|
572
|
+
align-items: center;
|
|
573
|
+
gap: 10px;
|
|
574
|
+
cursor: pointer;
|
|
575
|
+
}
|
|
576
|
+
.toggle-switch input[type="checkbox"] {
|
|
577
|
+
position: absolute;
|
|
578
|
+
opacity: 0;
|
|
579
|
+
width: 0;
|
|
580
|
+
height: 0;
|
|
581
|
+
}
|
|
582
|
+
.toggle-track {
|
|
583
|
+
width: 44px;
|
|
584
|
+
height: 24px;
|
|
585
|
+
background: #cbd5e1;
|
|
586
|
+
border-radius: 12px;
|
|
587
|
+
position: relative;
|
|
588
|
+
transition: background 0.2s ease;
|
|
589
|
+
flex-shrink: 0;
|
|
590
|
+
}
|
|
591
|
+
.toggle-track::after {
|
|
592
|
+
content: '';
|
|
593
|
+
position: absolute;
|
|
594
|
+
top: 2px;
|
|
595
|
+
left: 2px;
|
|
596
|
+
width: 20px;
|
|
597
|
+
height: 20px;
|
|
598
|
+
background: #fff;
|
|
599
|
+
border-radius: 50%;
|
|
600
|
+
transition: transform 0.2s ease;
|
|
601
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
|
|
602
|
+
}
|
|
603
|
+
.toggle-switch input[type="checkbox"]:checked + .toggle-track {
|
|
604
|
+
background: var(--color-success);
|
|
605
|
+
}
|
|
606
|
+
.toggle-switch input[type="checkbox"]:checked + .toggle-track::after {
|
|
607
|
+
transform: translateX(20px);
|
|
608
|
+
}
|
|
609
|
+
.toggle-label-text {
|
|
610
|
+
font-size: 14px;
|
|
611
|
+
font-weight: 500;
|
|
612
|
+
color: var(--color-text);
|
|
613
|
+
user-select: none;
|
|
277
614
|
}
|
|
278
615
|
|
|
279
616
|
/* User IDs Tag Input */
|
|
280
617
|
.user-ids-input-container {
|
|
281
|
-
border: 1px solid
|
|
282
|
-
border-radius:
|
|
283
|
-
padding: 8px;
|
|
284
|
-
background:
|
|
285
|
-
min-height:
|
|
618
|
+
border: 1px solid var(--color-border);
|
|
619
|
+
border-radius: var(--radius-sm);
|
|
620
|
+
padding: 6px 8px;
|
|
621
|
+
background: var(--color-surface);
|
|
622
|
+
min-height: 42px;
|
|
286
623
|
display: flex;
|
|
287
624
|
flex-wrap: wrap;
|
|
288
625
|
align-items: center;
|
|
289
626
|
gap: 6px;
|
|
627
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
628
|
+
}
|
|
629
|
+
.user-ids-input-container:focus-within {
|
|
630
|
+
border-color: var(--color-primary);
|
|
631
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
290
632
|
}
|
|
291
633
|
.user-ids-tags {
|
|
292
634
|
display: flex;
|
|
293
635
|
flex-wrap: wrap;
|
|
294
|
-
gap:
|
|
636
|
+
gap: 4px;
|
|
295
637
|
flex: 1;
|
|
296
638
|
}
|
|
297
639
|
.user-id-tag {
|
|
298
640
|
display: inline-flex;
|
|
299
641
|
align-items: center;
|
|
300
|
-
gap:
|
|
301
|
-
background:
|
|
642
|
+
gap: 4px;
|
|
643
|
+
background: var(--color-primary);
|
|
302
644
|
color: #fff;
|
|
303
|
-
padding:
|
|
304
|
-
border-radius:
|
|
305
|
-
font-size:
|
|
645
|
+
padding: 2px 8px;
|
|
646
|
+
border-radius: 100px;
|
|
647
|
+
font-size: 12px;
|
|
306
648
|
font-weight: 500;
|
|
307
649
|
}
|
|
650
|
+
.user-id-tag.excluded {
|
|
651
|
+
background: var(--color-danger);
|
|
652
|
+
}
|
|
308
653
|
.tag-remove {
|
|
309
654
|
cursor: pointer;
|
|
310
|
-
font-size:
|
|
655
|
+
font-size: 14px;
|
|
311
656
|
line-height: 1;
|
|
312
657
|
font-weight: bold;
|
|
313
|
-
opacity: 0.
|
|
314
|
-
transition: opacity 0.
|
|
315
|
-
|
|
316
|
-
.tag-remove:hover {
|
|
317
|
-
opacity: 1;
|
|
658
|
+
opacity: 0.7;
|
|
659
|
+
transition: opacity 0.15s;
|
|
660
|
+
margin-left: 2px;
|
|
318
661
|
}
|
|
662
|
+
.tag-remove:hover { opacity: 1; }
|
|
319
663
|
.user-ids-input {
|
|
320
664
|
flex: 1;
|
|
321
665
|
min-width: 150px;
|
|
322
666
|
border: none;
|
|
323
667
|
outline: none;
|
|
324
|
-
padding: 6px
|
|
668
|
+
padding: 4px 6px;
|
|
325
669
|
font-size: 14px;
|
|
670
|
+
font-family: var(--font-stack);
|
|
671
|
+
color: var(--color-text);
|
|
672
|
+
background: transparent;
|
|
326
673
|
}
|
|
327
|
-
.user-ids-input:
|
|
328
|
-
outline: none;
|
|
329
|
-
}
|
|
330
|
-
.user-ids-input-container:focus-within {
|
|
331
|
-
border-color: #3498db;
|
|
332
|
-
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
|
333
|
-
}
|
|
674
|
+
.user-ids-input::placeholder { color: var(--color-text-muted); }
|
|
334
675
|
|
|
335
|
-
/*
|
|
676
|
+
/* Checkbox search interface */
|
|
336
677
|
.tags-search-container {
|
|
337
678
|
display: flex;
|
|
338
679
|
align-items: center;
|
|
339
680
|
gap: 12px;
|
|
340
|
-
margin-bottom:
|
|
681
|
+
margin-bottom: 8px;
|
|
341
682
|
}
|
|
342
683
|
.tags-search-input {
|
|
343
684
|
flex: 1;
|
|
344
|
-
padding:
|
|
345
|
-
border: 1px solid
|
|
346
|
-
border-radius:
|
|
347
|
-
font-size:
|
|
348
|
-
|
|
685
|
+
padding: 8px 12px;
|
|
686
|
+
border: 1px solid var(--color-border);
|
|
687
|
+
border-radius: var(--radius-sm);
|
|
688
|
+
font-size: 13px;
|
|
689
|
+
font-family: var(--font-stack);
|
|
690
|
+
color: var(--color-text);
|
|
691
|
+
background: var(--color-surface);
|
|
692
|
+
outline: none;
|
|
693
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
349
694
|
}
|
|
350
695
|
.tags-search-input:focus {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
|
696
|
+
border-color: var(--color-primary);
|
|
697
|
+
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
|
354
698
|
}
|
|
699
|
+
.tags-search-input::placeholder { color: var(--color-text-muted); }
|
|
355
700
|
.tags-count {
|
|
356
|
-
font-size:
|
|
357
|
-
color:
|
|
701
|
+
font-size: 12px;
|
|
702
|
+
color: var(--color-text-muted);
|
|
358
703
|
white-space: nowrap;
|
|
359
704
|
}
|
|
360
705
|
.tags-checkbox-container {
|
|
361
|
-
max-height:
|
|
706
|
+
max-height: 240px;
|
|
362
707
|
overflow-y: auto;
|
|
363
|
-
border: 1px solid
|
|
364
|
-
border-radius:
|
|
365
|
-
padding:
|
|
366
|
-
background:
|
|
708
|
+
border: 1px solid var(--color-border);
|
|
709
|
+
border-radius: var(--radius-sm);
|
|
710
|
+
padding: 8px;
|
|
711
|
+
background: var(--color-bg);
|
|
367
712
|
display: flex;
|
|
368
713
|
flex-direction: column;
|
|
369
|
-
gap:
|
|
370
|
-
}
|
|
371
|
-
.tags-checkbox-container::-webkit-scrollbar {
|
|
372
|
-
width: 8px;
|
|
373
|
-
}
|
|
374
|
-
.tags-checkbox-container::-webkit-scrollbar-track {
|
|
375
|
-
background: #f1f1f1;
|
|
376
|
-
border-radius: 4px;
|
|
714
|
+
gap: 2px;
|
|
377
715
|
}
|
|
716
|
+
.tags-checkbox-container::-webkit-scrollbar { width: 6px; }
|
|
717
|
+
.tags-checkbox-container::-webkit-scrollbar-track { background: transparent; }
|
|
378
718
|
.tags-checkbox-container::-webkit-scrollbar-thumb {
|
|
379
|
-
background: #
|
|
380
|
-
border-radius:
|
|
381
|
-
}
|
|
382
|
-
.tags-checkbox-container::-webkit-scrollbar-thumb:hover {
|
|
383
|
-
background: #999;
|
|
719
|
+
background: #cbd5e1;
|
|
720
|
+
border-radius: 3px;
|
|
384
721
|
}
|
|
722
|
+
.tags-checkbox-container::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
|
385
723
|
.tag-checkbox-label {
|
|
386
724
|
display: inline-flex;
|
|
387
725
|
align-items: center;
|
|
388
726
|
cursor: pointer;
|
|
389
727
|
padding: 6px 8px;
|
|
390
|
-
border-radius:
|
|
391
|
-
transition: background-color 0.
|
|
392
|
-
}
|
|
393
|
-
.tag-checkbox-label:hover {
|
|
394
|
-
background-color: #f0f0f0;
|
|
395
|
-
}
|
|
396
|
-
.tag-checkbox-label.hidden {
|
|
397
|
-
display: none;
|
|
398
|
-
}
|
|
399
|
-
.tag-checkbox {
|
|
400
|
-
margin-right: 8px;
|
|
401
|
-
cursor: pointer;
|
|
728
|
+
border-radius: var(--radius-sm);
|
|
729
|
+
transition: background-color 0.1s;
|
|
402
730
|
}
|
|
731
|
+
.tag-checkbox-label:hover { background-color: var(--color-surface); }
|
|
732
|
+
.tag-checkbox-label.hidden { display: none; }
|
|
733
|
+
.tag-checkbox { margin-right: 8px; cursor: pointer; accent-color: var(--color-primary); }
|
|
403
734
|
.tag-checkbox-text {
|
|
404
|
-
font-size:
|
|
405
|
-
color:
|
|
735
|
+
font-size: 13px;
|
|
736
|
+
color: var(--color-text);
|
|
406
737
|
user-select: none;
|
|
407
738
|
}
|
|
408
|
-
.tag-checkbox:checked + .tag-checkbox-text
|
|
409
|
-
font-weight: 500;
|
|
410
|
-
color: #1976d2;
|
|
411
|
-
}
|
|
739
|
+
.tag-checkbox:checked + .tag-checkbox-text,
|
|
412
740
|
.tag-checkbox:checked ~ .tag-checkbox-text {
|
|
413
741
|
font-weight: 500;
|
|
414
|
-
color:
|
|
742
|
+
color: var(--color-primary);
|
|
415
743
|
}
|
|
416
|
-
/* Highlight checked labels */
|
|
417
744
|
.tag-checkbox-label:has(.tag-checkbox:checked) {
|
|
418
|
-
background-color:
|
|
745
|
+
background-color: var(--color-primary-light);
|
|
419
746
|
}
|
|
420
|
-
/* Fallback for browsers without :has() support */
|
|
421
747
|
.tag-checkbox-label.checked {
|
|
422
|
-
background-color:
|
|
748
|
+
background-color: var(--color-primary-light);
|
|
423
749
|
}
|
|
424
750
|
.tag-checkbox-label.checked .tag-checkbox-text {
|
|
425
751
|
font-weight: 500;
|
|
426
|
-
color:
|
|
752
|
+
color: var(--color-primary);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/* Exclusion accent */
|
|
756
|
+
.exclusion-border {
|
|
757
|
+
border-left: 3px solid var(--color-danger);
|
|
758
|
+
padding-left: 20px;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* Page title */
|
|
762
|
+
.page-title {
|
|
763
|
+
font-size: 22px;
|
|
764
|
+
font-weight: 700;
|
|
765
|
+
color: var(--color-text);
|
|
766
|
+
letter-spacing: -0.02em;
|
|
767
|
+
}
|
|
768
|
+
.page-subtitle {
|
|
769
|
+
font-size: 14px;
|
|
770
|
+
color: var(--color-text-secondary);
|
|
771
|
+
margin-top: 4px;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/* Link */
|
|
775
|
+
.link-subtle {
|
|
776
|
+
color: var(--color-primary);
|
|
777
|
+
text-decoration: none;
|
|
778
|
+
font-size: 13px;
|
|
779
|
+
font-weight: 500;
|
|
427
780
|
}
|
|
781
|
+
.link-subtle:hover { text-decoration: underline; }
|
|
428
782
|
|
|
429
783
|
/* Mobile Responsive */
|
|
430
784
|
@media (max-width: 768px) {
|
|
431
|
-
.container {
|
|
432
|
-
|
|
433
|
-
}
|
|
434
|
-
header {
|
|
435
|
-
padding: 15px 0;
|
|
436
|
-
}
|
|
437
|
-
header h1 {
|
|
438
|
-
font-size: 20px;
|
|
439
|
-
}
|
|
440
|
-
.card {
|
|
441
|
-
padding: 16px;
|
|
442
|
-
}
|
|
785
|
+
.container { padding: 16px; }
|
|
786
|
+
.nav-inner { padding: 0 16px; }
|
|
787
|
+
.card-body { padding: 16px; }
|
|
443
788
|
.card-header {
|
|
444
789
|
flex-direction: column;
|
|
445
790
|
align-items: flex-start;
|
|
446
791
|
gap: 12px;
|
|
792
|
+
padding: 16px;
|
|
447
793
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
table {
|
|
452
|
-
display: block;
|
|
453
|
-
overflow-x: auto;
|
|
454
|
-
-webkit-overflow-scrolling: touch;
|
|
455
|
-
}
|
|
456
|
-
thead {
|
|
457
|
-
display: none;
|
|
458
|
-
}
|
|
459
|
-
tr {
|
|
794
|
+
table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
795
|
+
thead { display: none; }
|
|
796
|
+
tbody tr {
|
|
460
797
|
display: block;
|
|
461
|
-
margin-bottom:
|
|
462
|
-
border: 1px solid
|
|
463
|
-
border-radius:
|
|
464
|
-
padding: 12px;
|
|
465
|
-
background:
|
|
798
|
+
margin-bottom: 12px;
|
|
799
|
+
border: 1px solid var(--color-border);
|
|
800
|
+
border-radius: var(--radius-md);
|
|
801
|
+
padding: 12px 16px;
|
|
802
|
+
background: var(--color-surface);
|
|
466
803
|
}
|
|
467
804
|
td {
|
|
468
805
|
display: block;
|
|
469
|
-
padding:
|
|
806
|
+
padding: 6px 0;
|
|
470
807
|
border: none;
|
|
471
|
-
text-align: left;
|
|
472
808
|
}
|
|
473
809
|
td:before {
|
|
474
810
|
content: attr(data-label);
|
|
475
|
-
font-weight:
|
|
476
|
-
color:
|
|
811
|
+
font-weight: 500;
|
|
812
|
+
color: var(--color-text-muted);
|
|
477
813
|
display: block;
|
|
478
|
-
margin-bottom:
|
|
479
|
-
font-size:
|
|
814
|
+
margin-bottom: 2px;
|
|
815
|
+
font-size: 11px;
|
|
480
816
|
text-transform: uppercase;
|
|
481
|
-
letter-spacing: 0.
|
|
817
|
+
letter-spacing: 0.05em;
|
|
482
818
|
}
|
|
483
819
|
td:last-child {
|
|
484
|
-
border-top: 1px solid
|
|
820
|
+
border-top: 1px solid var(--color-border);
|
|
485
821
|
margin-top: 8px;
|
|
486
|
-
padding-top:
|
|
487
|
-
}
|
|
488
|
-
.btn-group {
|
|
489
|
-
flex-direction: column;
|
|
490
|
-
width: 100%;
|
|
491
|
-
}
|
|
492
|
-
.btn-group .btn {
|
|
493
|
-
width: 100%;
|
|
494
|
-
}
|
|
495
|
-
.feature-details {
|
|
496
|
-
grid-template-columns: 1fr;
|
|
497
|
-
}
|
|
498
|
-
.stats-grid {
|
|
499
|
-
grid-template-columns: 1fr;
|
|
500
|
-
}
|
|
501
|
-
.card-header > div {
|
|
502
|
-
width: 100%;
|
|
503
|
-
}
|
|
504
|
-
.card-header > div .btn {
|
|
505
|
-
width: 100%;
|
|
506
|
-
margin-bottom: 8px;
|
|
507
|
-
}
|
|
508
|
-
.user-ids-input-container {
|
|
509
|
-
min-height: 40px;
|
|
510
|
-
padding: 6px;
|
|
511
|
-
}
|
|
512
|
-
.user-ids-input {
|
|
513
|
-
min-width: 100px;
|
|
514
|
-
font-size: 16px; /* Prevents zoom on iOS */
|
|
515
|
-
}
|
|
516
|
-
.tags-search-container {
|
|
517
|
-
flex-direction: column;
|
|
518
|
-
align-items: stretch;
|
|
519
|
-
}
|
|
520
|
-
.tags-count {
|
|
521
|
-
text-align: right;
|
|
522
|
-
margin-top: 4px;
|
|
523
|
-
}
|
|
524
|
-
.tags-checkbox-container {
|
|
525
|
-
max-height: 200px;
|
|
822
|
+
padding-top: 10px;
|
|
526
823
|
}
|
|
824
|
+
.btn-group { flex-direction: column; width: 100%; }
|
|
825
|
+
.btn-group .btn { width: 100%; }
|
|
826
|
+
.detail-row { flex-direction: column; }
|
|
827
|
+
.stats-grid { grid-template-columns: 1fr; }
|
|
828
|
+
.filter-bar { flex-direction: column !important; gap: 10px !important; }
|
|
829
|
+
.filter-bar > * { flex: 1 1 100% !important; min-width: 0 !important; }
|
|
830
|
+
.user-ids-input { font-size: 16px; }
|
|
831
|
+
.tags-search-container { flex-direction: column; align-items: stretch; }
|
|
832
|
+
.tags-count { text-align: right; }
|
|
833
|
+
.tags-checkbox-container { max-height: 200px; }
|
|
834
|
+
.exclusion-border { padding-left: 16px; }
|
|
527
835
|
}
|
|
528
836
|
|
|
529
|
-
/* Tablet */
|
|
530
837
|
@media (min-width: 769px) and (max-width: 1024px) {
|
|
531
|
-
.container {
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
table {
|
|
535
|
-
font-size: 14px;
|
|
536
|
-
}
|
|
537
|
-
th, td {
|
|
538
|
-
padding: 10px 8px;
|
|
539
|
-
}
|
|
838
|
+
.container { padding: 20px; }
|
|
839
|
+
th, td { padding: 10px 12px; }
|
|
540
840
|
}
|
|
541
841
|
</style>
|
|
542
842
|
</head>
|
|
543
843
|
<body>
|
|
544
|
-
<
|
|
545
|
-
<div class="
|
|
546
|
-
<
|
|
844
|
+
<nav class="nav">
|
|
845
|
+
<div class="nav-inner">
|
|
846
|
+
<a href="<%= magick_admin_ui.root_path %>" class="nav-brand">
|
|
847
|
+
<span class="nav-brand-icon">M</span>
|
|
848
|
+
Magick
|
|
849
|
+
</a>
|
|
547
850
|
</div>
|
|
548
|
-
</
|
|
851
|
+
</nav>
|
|
549
852
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
<
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
853
|
+
<% if notice %>
|
|
854
|
+
<div class="flash-banner flash-banner-success" data-flash>
|
|
855
|
+
<span><%= notice %></span>
|
|
856
|
+
<button class="flash-dismiss" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
|
|
857
|
+
</div>
|
|
858
|
+
<% end %>
|
|
859
|
+
<% if alert %>
|
|
860
|
+
<div class="flash-banner flash-banner-danger" data-flash>
|
|
861
|
+
<span><%= alert %></span>
|
|
862
|
+
<button class="flash-dismiss" onclick="this.parentElement.remove()" aria-label="Dismiss">×</button>
|
|
863
|
+
</div>
|
|
864
|
+
<% end %>
|
|
557
865
|
|
|
866
|
+
<div class="container">
|
|
558
867
|
<%= yield %>
|
|
559
868
|
</div>
|
|
560
869
|
|
|
561
870
|
<script>
|
|
871
|
+
// Auto-dismiss flash messages
|
|
872
|
+
document.querySelectorAll('[data-flash]').forEach(function(el) {
|
|
873
|
+
setTimeout(function() {
|
|
874
|
+
el.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
|
875
|
+
el.style.opacity = '0';
|
|
876
|
+
el.style.transform = 'translateY(-8px)';
|
|
877
|
+
setTimeout(function() { el.remove(); }, 300);
|
|
878
|
+
}, 5000);
|
|
879
|
+
});
|
|
880
|
+
|
|
562
881
|
// User IDs Tag Input Handler
|
|
563
882
|
(function() {
|
|
564
883
|
const input = document.getElementById('user_ids_input');
|
|
@@ -569,7 +888,7 @@
|
|
|
569
888
|
|
|
570
889
|
function updateHiddenInput() {
|
|
571
890
|
const tags = Array.from(tagsContainer.querySelectorAll('.user-id-tag'));
|
|
572
|
-
const userIds = tags.map(tag => tag.textContent.trim().replace('
|
|
891
|
+
const userIds = tags.map(tag => tag.textContent.trim().replace('\u00d7', '').trim()).filter(id => id);
|
|
573
892
|
hiddenInput.value = userIds.join(',');
|
|
574
893
|
}
|
|
575
894
|
|
|
@@ -577,14 +896,13 @@
|
|
|
577
896
|
userId = userId.trim();
|
|
578
897
|
if (!userId) return;
|
|
579
898
|
|
|
580
|
-
// Check if already exists
|
|
581
899
|
const existing = Array.from(tagsContainer.querySelectorAll('.user-id-tag'))
|
|
582
|
-
.some(tag => tag.textContent.trim().replace('
|
|
900
|
+
.some(tag => tag.textContent.trim().replace('\u00d7', '').trim() === userId);
|
|
583
901
|
if (existing) return;
|
|
584
902
|
|
|
585
903
|
const tag = document.createElement('span');
|
|
586
904
|
tag.className = 'user-id-tag';
|
|
587
|
-
tag.innerHTML = userId + ' <span class="tag-remove" data-user-id="' + userId + '"
|
|
905
|
+
tag.innerHTML = userId + ' <span class="tag-remove" data-user-id="' + userId + '">\u00d7</span>';
|
|
588
906
|
|
|
589
907
|
const removeBtn = tag.querySelector('.tag-remove');
|
|
590
908
|
removeBtn.addEventListener('click', function() {
|
|
@@ -601,7 +919,6 @@
|
|
|
601
919
|
e.preventDefault();
|
|
602
920
|
const value = input.value.trim();
|
|
603
921
|
if (value) {
|
|
604
|
-
// Support comma-separated values
|
|
605
922
|
value.split(',').forEach(id => {
|
|
606
923
|
const trimmed = id.trim();
|
|
607
924
|
if (trimmed) addTag(trimmed);
|
|
@@ -611,7 +928,6 @@
|
|
|
611
928
|
}
|
|
612
929
|
});
|
|
613
930
|
|
|
614
|
-
// Handle paste with comma-separated values
|
|
615
931
|
input.addEventListener('paste', function(e) {
|
|
616
932
|
setTimeout(function() {
|
|
617
933
|
const value = input.value.trim();
|
|
@@ -625,7 +941,6 @@
|
|
|
625
941
|
}, 10);
|
|
626
942
|
});
|
|
627
943
|
|
|
628
|
-
// Remove existing tags when clicked
|
|
629
944
|
tagsContainer.querySelectorAll('.tag-remove').forEach(btn => {
|
|
630
945
|
btn.addEventListener('click', function() {
|
|
631
946
|
this.parentElement.remove();
|
|
@@ -656,7 +971,6 @@
|
|
|
656
971
|
const checkbox = label.querySelector('.tag-checkbox');
|
|
657
972
|
const isChecked = checkbox && checkbox.checked;
|
|
658
973
|
|
|
659
|
-
// Match against tag name, id, or display text
|
|
660
974
|
const matches = !searchTerm ||
|
|
661
975
|
tagName.includes(searchTerm) ||
|
|
662
976
|
tagId.includes(searchTerm) ||
|
|
@@ -671,14 +985,12 @@
|
|
|
671
985
|
}
|
|
672
986
|
});
|
|
673
987
|
|
|
674
|
-
// Sort visible labels: checked first, then unchecked
|
|
675
988
|
visibleLabels.sort((a, b) => {
|
|
676
989
|
if (a.checked && !b.checked) return -1;
|
|
677
990
|
if (!a.checked && b.checked) return 1;
|
|
678
991
|
return 0;
|
|
679
992
|
});
|
|
680
993
|
|
|
681
|
-
// Reorder DOM: checked items first, then unchecked, then hidden
|
|
682
994
|
visibleLabels.forEach(({ label }) => {
|
|
683
995
|
checkboxContainer.appendChild(label);
|
|
684
996
|
});
|
|
@@ -689,10 +1001,8 @@
|
|
|
689
1001
|
visibleCount.textContent = visibleLabels.length;
|
|
690
1002
|
}
|
|
691
1003
|
|
|
692
|
-
// Filter on input
|
|
693
1004
|
searchInput.addEventListener('input', filterTags);
|
|
694
1005
|
|
|
695
|
-
// Clear search on Escape key
|
|
696
1006
|
searchInput.addEventListener('keydown', function(e) {
|
|
697
1007
|
if (e.key === 'Escape') {
|
|
698
1008
|
searchInput.value = '';
|
|
@@ -701,7 +1011,6 @@
|
|
|
701
1011
|
}
|
|
702
1012
|
});
|
|
703
1013
|
|
|
704
|
-
// Update checked state classes and reorder on change
|
|
705
1014
|
checkboxContainer.querySelectorAll('.tag-checkbox').forEach(checkbox => {
|
|
706
1015
|
function updateCheckedClass() {
|
|
707
1016
|
const label = checkbox.closest('.tag-checkbox-label');
|
|
@@ -712,14 +1021,155 @@
|
|
|
712
1021
|
label.classList.remove('checked');
|
|
713
1022
|
label.setAttribute('data-checked', 'false');
|
|
714
1023
|
}
|
|
715
|
-
// Reorder after change to keep checked items on top
|
|
716
1024
|
filterTags();
|
|
717
1025
|
}
|
|
718
1026
|
checkbox.addEventListener('change', updateCheckedClass);
|
|
719
|
-
updateCheckedClass();
|
|
1027
|
+
updateCheckedClass();
|
|
720
1028
|
});
|
|
721
1029
|
})();
|
|
722
1030
|
|
|
1031
|
+
// Excluded User IDs Tag Input Handler
|
|
1032
|
+
(function() {
|
|
1033
|
+
const input = document.getElementById('excluded_user_ids_input');
|
|
1034
|
+
const tagsContainer = document.getElementById('excluded_user_ids_tags');
|
|
1035
|
+
const hiddenInput = document.getElementById('excluded_user_ids_hidden');
|
|
1036
|
+
|
|
1037
|
+
if (!input || !tagsContainer || !hiddenInput) return;
|
|
1038
|
+
|
|
1039
|
+
function updateHiddenInput() {
|
|
1040
|
+
const tags = Array.from(tagsContainer.querySelectorAll('.user-id-tag'));
|
|
1041
|
+
const userIds = tags.map(tag => tag.textContent.trim().replace('\u00d7', '').trim()).filter(id => id);
|
|
1042
|
+
hiddenInput.value = userIds.join(',');
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
function addTag(userId) {
|
|
1046
|
+
userId = userId.trim();
|
|
1047
|
+
if (!userId) return;
|
|
1048
|
+
|
|
1049
|
+
const existing = Array.from(tagsContainer.querySelectorAll('.user-id-tag'))
|
|
1050
|
+
.some(tag => tag.textContent.trim().replace('\u00d7', '').trim() === userId);
|
|
1051
|
+
if (existing) return;
|
|
1052
|
+
|
|
1053
|
+
const tag = document.createElement('span');
|
|
1054
|
+
tag.className = 'user-id-tag excluded';
|
|
1055
|
+
tag.innerHTML = userId + ' <span class="tag-remove" data-user-id="' + userId + '">\u00d7</span>';
|
|
1056
|
+
|
|
1057
|
+
const removeBtn = tag.querySelector('.tag-remove');
|
|
1058
|
+
removeBtn.addEventListener('click', function() {
|
|
1059
|
+
tag.remove();
|
|
1060
|
+
updateHiddenInput();
|
|
1061
|
+
});
|
|
1062
|
+
|
|
1063
|
+
tagsContainer.appendChild(tag);
|
|
1064
|
+
updateHiddenInput();
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
input.addEventListener('keydown', function(e) {
|
|
1068
|
+
if (e.key === 'Enter' || e.keyCode === 13) {
|
|
1069
|
+
e.preventDefault();
|
|
1070
|
+
const value = input.value.trim();
|
|
1071
|
+
if (value) {
|
|
1072
|
+
value.split(',').forEach(id => {
|
|
1073
|
+
const trimmed = id.trim();
|
|
1074
|
+
if (trimmed) addTag(trimmed);
|
|
1075
|
+
});
|
|
1076
|
+
input.value = '';
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
input.addEventListener('paste', function(e) {
|
|
1082
|
+
setTimeout(function() {
|
|
1083
|
+
const value = input.value.trim();
|
|
1084
|
+
if (value.includes(',')) {
|
|
1085
|
+
value.split(',').forEach(id => {
|
|
1086
|
+
const trimmed = id.trim();
|
|
1087
|
+
if (trimmed) addTag(trimmed);
|
|
1088
|
+
});
|
|
1089
|
+
input.value = '';
|
|
1090
|
+
}
|
|
1091
|
+
}, 10);
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
tagsContainer.querySelectorAll('.tag-remove').forEach(btn => {
|
|
1095
|
+
btn.addEventListener('click', function() {
|
|
1096
|
+
this.parentElement.remove();
|
|
1097
|
+
updateHiddenInput();
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
})();
|
|
1101
|
+
|
|
1102
|
+
// Generic checkbox search filter (reusable for roles, tags, excluded roles, excluded tags)
|
|
1103
|
+
function initCheckboxSearchFilter(searchId, containerId, visibleCountId, totalCountId) {
|
|
1104
|
+
const searchInput = document.getElementById(searchId);
|
|
1105
|
+
const checkboxContainer = document.getElementById(containerId);
|
|
1106
|
+
const visibleCount = document.getElementById(visibleCountId);
|
|
1107
|
+
const totalCount = document.getElementById(totalCountId);
|
|
1108
|
+
|
|
1109
|
+
if (!searchInput || !checkboxContainer || !visibleCount || !totalCount) return;
|
|
1110
|
+
|
|
1111
|
+
function filterItems() {
|
|
1112
|
+
const searchTerm = searchInput.value.toLowerCase().trim();
|
|
1113
|
+
const labels = Array.from(checkboxContainer.querySelectorAll('.tag-checkbox-label'));
|
|
1114
|
+
let visibleLabels = [];
|
|
1115
|
+
let hiddenLabels = [];
|
|
1116
|
+
|
|
1117
|
+
labels.forEach(label => {
|
|
1118
|
+
const name = label.getAttribute('data-tag-name') || label.getAttribute('data-role-name') || '';
|
|
1119
|
+
const id = label.getAttribute('data-tag-id') || '';
|
|
1120
|
+
const text = (label.querySelector('.tag-checkbox-text')?.textContent || '').toLowerCase();
|
|
1121
|
+
const checkbox = label.querySelector('.tag-checkbox');
|
|
1122
|
+
const isChecked = checkbox && checkbox.checked;
|
|
1123
|
+
|
|
1124
|
+
const matches = !searchTerm || name.includes(searchTerm) || id.includes(searchTerm) || text.includes(searchTerm);
|
|
1125
|
+
|
|
1126
|
+
if (matches) {
|
|
1127
|
+
label.classList.remove('hidden');
|
|
1128
|
+
visibleLabels.push({ label: label, checked: isChecked });
|
|
1129
|
+
} else {
|
|
1130
|
+
label.classList.add('hidden');
|
|
1131
|
+
hiddenLabels.push(label);
|
|
1132
|
+
}
|
|
1133
|
+
});
|
|
1134
|
+
|
|
1135
|
+
visibleLabels.sort((a, b) => (a.checked === b.checked) ? 0 : a.checked ? -1 : 1);
|
|
1136
|
+
|
|
1137
|
+
visibleLabels.forEach(({ label }) => checkboxContainer.appendChild(label));
|
|
1138
|
+
hiddenLabels.forEach(label => checkboxContainer.appendChild(label));
|
|
1139
|
+
|
|
1140
|
+
visibleCount.textContent = visibleLabels.length;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
searchInput.addEventListener('input', filterItems);
|
|
1144
|
+
searchInput.addEventListener('keydown', function(e) {
|
|
1145
|
+
if (e.key === 'Escape') {
|
|
1146
|
+
searchInput.value = '';
|
|
1147
|
+
filterItems();
|
|
1148
|
+
searchInput.blur();
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
checkboxContainer.querySelectorAll('.tag-checkbox').forEach(checkbox => {
|
|
1153
|
+
function updateCheckedClass() {
|
|
1154
|
+
const label = checkbox.closest('.tag-checkbox-label');
|
|
1155
|
+
if (checkbox.checked) {
|
|
1156
|
+
label.classList.add('checked');
|
|
1157
|
+
label.setAttribute('data-checked', 'true');
|
|
1158
|
+
} else {
|
|
1159
|
+
label.classList.remove('checked');
|
|
1160
|
+
label.setAttribute('data-checked', 'false');
|
|
1161
|
+
}
|
|
1162
|
+
filterItems();
|
|
1163
|
+
}
|
|
1164
|
+
checkbox.addEventListener('change', updateCheckedClass);
|
|
1165
|
+
updateCheckedClass();
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Initialize excluded roles and tags search filters
|
|
1170
|
+
initCheckboxSearchFilter('excluded_roles_search', 'excluded_roles_checkbox_container', 'excluded_roles_visible_count', 'excluded_roles_total_count');
|
|
1171
|
+
initCheckboxSearchFilter('excluded_tags_search', 'excluded_tags_checkbox_container', 'excluded_tags_visible_count', 'excluded_tags_total_count');
|
|
1172
|
+
|
|
723
1173
|
// Roles Search Filter
|
|
724
1174
|
(function() {
|
|
725
1175
|
const searchInput = document.getElementById('roles_search');
|
|
@@ -741,7 +1191,6 @@
|
|
|
741
1191
|
const checkbox = label.querySelector('.tag-checkbox');
|
|
742
1192
|
const isChecked = checkbox && checkbox.checked;
|
|
743
1193
|
|
|
744
|
-
// Match against role name or display text
|
|
745
1194
|
const matches = !searchTerm ||
|
|
746
1195
|
roleName.includes(searchTerm) ||
|
|
747
1196
|
roleText.includes(searchTerm);
|
|
@@ -755,14 +1204,12 @@
|
|
|
755
1204
|
}
|
|
756
1205
|
});
|
|
757
1206
|
|
|
758
|
-
// Sort visible labels: checked first, then unchecked
|
|
759
1207
|
visibleLabels.sort((a, b) => {
|
|
760
1208
|
if (a.checked && !b.checked) return -1;
|
|
761
1209
|
if (!a.checked && b.checked) return 1;
|
|
762
1210
|
return 0;
|
|
763
1211
|
});
|
|
764
1212
|
|
|
765
|
-
// Reorder DOM: checked items first, then unchecked, then hidden
|
|
766
1213
|
visibleLabels.forEach(({ label }) => {
|
|
767
1214
|
checkboxContainer.appendChild(label);
|
|
768
1215
|
});
|
|
@@ -773,10 +1220,8 @@
|
|
|
773
1220
|
visibleCount.textContent = visibleLabels.length;
|
|
774
1221
|
}
|
|
775
1222
|
|
|
776
|
-
// Filter on input
|
|
777
1223
|
searchInput.addEventListener('input', filterRoles);
|
|
778
1224
|
|
|
779
|
-
// Clear search on Escape key
|
|
780
1225
|
searchInput.addEventListener('keydown', function(e) {
|
|
781
1226
|
if (e.key === 'Escape') {
|
|
782
1227
|
searchInput.value = '';
|
|
@@ -785,7 +1230,6 @@
|
|
|
785
1230
|
}
|
|
786
1231
|
});
|
|
787
1232
|
|
|
788
|
-
// Update checked state classes and reorder on change
|
|
789
1233
|
checkboxContainer.querySelectorAll('.tag-checkbox').forEach(checkbox => {
|
|
790
1234
|
function updateCheckedClass() {
|
|
791
1235
|
const label = checkbox.closest('.tag-checkbox-label');
|
|
@@ -796,13 +1240,57 @@
|
|
|
796
1240
|
label.classList.remove('checked');
|
|
797
1241
|
label.setAttribute('data-checked', 'false');
|
|
798
1242
|
}
|
|
799
|
-
// Reorder after change to keep checked items on top
|
|
800
1243
|
filterRoles();
|
|
801
1244
|
}
|
|
802
1245
|
checkbox.addEventListener('change', updateCheckedClass);
|
|
803
|
-
updateCheckedClass();
|
|
1246
|
+
updateCheckedClass();
|
|
804
1247
|
});
|
|
805
1248
|
})();
|
|
1249
|
+
|
|
1250
|
+
// Index page: instant search filter
|
|
1251
|
+
(function() {
|
|
1252
|
+
const searchInput = document.getElementById('feature_search_input');
|
|
1253
|
+
const groupSelect = document.getElementById('feature_group_select');
|
|
1254
|
+
const tableBody = document.getElementById('features_table_body');
|
|
1255
|
+
const emptyState = document.getElementById('features_empty_filter');
|
|
1256
|
+
const featureCount = document.getElementById('feature_count_text');
|
|
1257
|
+
|
|
1258
|
+
if (!searchInput || !tableBody) return;
|
|
1259
|
+
|
|
1260
|
+
function filterFeatures() {
|
|
1261
|
+
const searchTerm = searchInput.value.toLowerCase().trim();
|
|
1262
|
+
const groupTerm = groupSelect ? groupSelect.value : '';
|
|
1263
|
+
const rows = Array.from(tableBody.querySelectorAll('tr[data-feature]'));
|
|
1264
|
+
let visibleCount = 0;
|
|
1265
|
+
|
|
1266
|
+
rows.forEach(row => {
|
|
1267
|
+
const name = (row.getAttribute('data-feature-name') || '').toLowerCase();
|
|
1268
|
+
const displayName = (row.getAttribute('data-feature-display') || '').toLowerCase();
|
|
1269
|
+
const desc = (row.getAttribute('data-feature-desc') || '').toLowerCase();
|
|
1270
|
+
const group = row.getAttribute('data-feature-group') || '';
|
|
1271
|
+
|
|
1272
|
+
const matchesSearch = !searchTerm || name.includes(searchTerm) || displayName.includes(searchTerm) || desc.includes(searchTerm);
|
|
1273
|
+
const matchesGroup = !groupTerm || group === groupTerm;
|
|
1274
|
+
|
|
1275
|
+
if (matchesSearch && matchesGroup) {
|
|
1276
|
+
row.style.display = '';
|
|
1277
|
+
visibleCount++;
|
|
1278
|
+
} else {
|
|
1279
|
+
row.style.display = 'none';
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
if (emptyState) {
|
|
1284
|
+
emptyState.style.display = visibleCount === 0 ? '' : 'none';
|
|
1285
|
+
}
|
|
1286
|
+
if (featureCount) {
|
|
1287
|
+
featureCount.textContent = visibleCount + ' feature' + (visibleCount !== 1 ? 's' : '');
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
searchInput.addEventListener('input', filterFeatures);
|
|
1292
|
+
if (groupSelect) groupSelect.addEventListener('change', filterFeatures);
|
|
1293
|
+
})();
|
|
806
1294
|
</script>
|
|
807
1295
|
</body>
|
|
808
1296
|
</html>
|