@vitronai/alethia 0.8.1 → 0.8.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.
@@ -1,114 +1,525 @@
1
1
  <!DOCTYPE html>
2
+ <!-- Note: <html> intentionally lacks lang attribute on the next line —
3
+ this page is a deliberate WCAG negative-test surface. Do not "fix". -->
2
4
  <html>
3
5
  <head>
4
6
  <meta charset="utf-8">
7
+ <!-- Note: <title> deliberately omitted to trigger WCAG 2.4.2. -->
5
8
  <style>
9
+ :root {
10
+ --bg-0: #0a0d14;
11
+ --bg-1: #11151f;
12
+ --bg-2: #181d2a;
13
+ --bg-3: #20263a;
14
+ --line: #262d42;
15
+ --line-soft: #1c2235;
16
+ --ink: #e6ecf5;
17
+ --ink-muted: #9aa6c2;
18
+ --ink-faint: #677295;
19
+ --ink-dim: #4a5577;
20
+ --amber: #fbbf24;
21
+ --amber-strong: #f59e0b;
22
+ --amber-soft: rgba(251,191,36,.12);
23
+ --rose: #f87171;
24
+ --rose-soft: rgba(248,113,113,.12);
25
+ --emerald: #34d399;
26
+ --emerald-soft: rgba(52,211,153,.12);
27
+ --indigo: #818cf8;
28
+ --indigo-soft: rgba(129,140,248,.12);
29
+ }
6
30
  * { box-sizing: border-box; margin: 0; padding: 0; }
7
- body { font-family: 'SF Mono', monospace; background: #080c14; color: #c8d6e5; min-height: 100vh; padding: 1.5rem; font-size: 0.85rem; }
8
- h1 { font-size: 1.2rem; color: #e2e8f0; margin-bottom: 0.3rem; }
9
- .subtitle { color: #5a7a9e; font-size: 0.8rem; margin-bottom: 1.5rem; }
10
- .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
11
- .panel { background: #0d1525; border: 1px solid #1a2744; border-radius: 8px; padding: 1rem; }
12
- .panel h2 { font-size: 0.85rem; color: #7eb8da; margin-bottom: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; }
13
- .violation { padding: 0.5rem 0; border-bottom: 1px solid #111d30; display: flex; justify-content: space-between; align-items: center; }
14
- .violation:last-child { border-bottom: none; }
15
- .violation-label { color: #c8d6e5; }
16
- .violation-code { font-size: 0.7rem; color: #ff8c00; }
17
- .expect-fail { font-size: 0.7rem; padding: 0.15rem 0.5rem; border-radius: 3px; background: #1a0808; color: #ff4444; border: 1px solid #ff4444; }
18
- button { padding: 0.35rem 0.7rem; border-radius: 4px; border: 1px solid #1a2744; background: #111d30; color: #7eb8da; cursor: pointer; font: inherit; font-size: 0.75rem; }
19
- input { padding: 0.35rem 0.5rem; border-radius: 4px; border: 1px solid #1a2744; background: #111d30; color: #e2e8f0; font: inherit; font-size: 0.8rem; width: 180px; }
20
- img { display: inline-block; width: 60px; height: 40px; background: #2a3a5a; border: 1px solid #4a6a9a; border-radius: 4px; margin: 0.3rem 0.3rem 0 0; }
21
- .low-contrast { color: #1a2744; }
22
- .tiny-text { font-size: 6px; }
23
- .score-bar { display: flex; gap: 1.5rem; padding: 0.8rem; background: #111d30; border-radius: 8px; border: 1px solid #1a2744; margin-bottom: 1rem; }
24
- .score-label { color: #5a7a9e; font-size: 0.8rem; }
25
- .score-value { font-size: 1.8rem; font-weight: 700; }
26
- .score-bad { color: #ff4444; }
27
- .full-width { grid-column: 1 / -1; }
31
+ html { color-scheme: dark; }
32
+ body {
33
+ font-family: ui-sans-serif, -apple-system, system-ui, "Inter", sans-serif;
34
+ background:
35
+ radial-gradient(1100px 600px at 0% -10%, rgba(251,191,36,.08), transparent 55%),
36
+ radial-gradient(900px 500px at 105% 110%, rgba(129,140,248,.05), transparent 55%),
37
+ var(--bg-0);
38
+ background-attachment: fixed;
39
+ color: var(--ink);
40
+ min-height: 100vh;
41
+ -webkit-font-smoothing: antialiased;
42
+ letter-spacing: -.005em;
43
+ font-size: 13.5px;
44
+ }
45
+
46
+ .topbar {
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: space-between;
50
+ padding: 12px 20px;
51
+ border-bottom: 1px solid var(--line-soft);
52
+ background: rgba(10,13,20,.7);
53
+ backdrop-filter: saturate(140%) blur(8px);
54
+ position: sticky;
55
+ top: 0;
56
+ z-index: 5;
57
+ }
58
+ .brand { display: flex; align-items: center; gap: 10px; font-weight: 600; }
59
+ .brand-mark {
60
+ width: 26px; height: 26px;
61
+ border-radius: 7px;
62
+ background: linear-gradient(135deg, var(--amber), var(--amber-strong));
63
+ display: grid;
64
+ place-items: center;
65
+ box-shadow: 0 4px 14px rgba(251,191,36,.3), inset 0 1px 0 rgba(255,255,255,.2);
66
+ }
67
+ .brand-mark svg { color: #2a1a05; }
68
+ .brand-name { letter-spacing: -.01em; }
69
+ .brand-eyebrow { font-size: 11px; color: var(--ink-faint); font-weight: 500; }
70
+ .neg-badge {
71
+ padding: 4px 11px;
72
+ border-radius: 999px;
73
+ background: var(--rose-soft);
74
+ border: 1px solid rgba(248,113,113,.4);
75
+ color: var(--rose);
76
+ font-size: 10.5px;
77
+ font-weight: 700;
78
+ letter-spacing: .1em;
79
+ text-transform: uppercase;
80
+ }
81
+
82
+ .container {
83
+ max-width: 1180px;
84
+ margin: 0 auto;
85
+ padding: 20px;
86
+ }
87
+
88
+ .lead-card {
89
+ padding: 16px 18px;
90
+ background: linear-gradient(135deg, rgba(251,191,36,.06), rgba(248,113,113,.04));
91
+ border: 1px solid rgba(251,191,36,.25);
92
+ border-radius: 12px;
93
+ margin-bottom: 16px;
94
+ display: flex;
95
+ gap: 14px;
96
+ align-items: center;
97
+ }
98
+ .lead-icon {
99
+ width: 38px; height: 38px;
100
+ border-radius: 10px;
101
+ background: var(--amber-soft);
102
+ border: 1px solid rgba(251,191,36,.3);
103
+ display: grid;
104
+ place-items: center;
105
+ flex-shrink: 0;
106
+ color: var(--amber);
107
+ }
108
+ .lead-card h1 {
109
+ font-size: 16px;
110
+ font-weight: 600;
111
+ color: #fff;
112
+ margin-bottom: 2px;
113
+ }
114
+ .lead-card p {
115
+ font-size: 13px;
116
+ color: var(--ink-muted);
117
+ }
118
+
119
+ .summary {
120
+ display: grid;
121
+ grid-template-columns: 1fr 1fr 1fr;
122
+ gap: 12px;
123
+ margin-bottom: 16px;
124
+ }
125
+ .summary-card {
126
+ background: var(--bg-1);
127
+ border: 1px solid var(--line);
128
+ border-radius: 12px;
129
+ padding: 14px 16px;
130
+ position: relative;
131
+ overflow: hidden;
132
+ transition: border-color .15s, transform .15s;
133
+ }
134
+ .summary-card:hover { border-color: #2c3550; transform: translateY(-1px); }
135
+ .summary-card::after {
136
+ content: "";
137
+ position: absolute;
138
+ inset: 0;
139
+ pointer-events: none;
140
+ background: radial-gradient(160px 90px at 100% 0%, var(--c-soft, rgba(255,255,255,.04)), transparent 70%);
141
+ }
142
+ .summary-card .head {
143
+ display: flex; align-items: center; gap: 6px;
144
+ font-size: 11px;
145
+ font-weight: 600;
146
+ letter-spacing: .08em;
147
+ text-transform: uppercase;
148
+ color: var(--ink-faint);
149
+ }
150
+ .summary-card .head .dot {
151
+ width: 6px; height: 6px;
152
+ border-radius: 50%;
153
+ background: var(--c, var(--ink-faint));
154
+ box-shadow: 0 0 8px var(--c, transparent);
155
+ }
156
+ .summary-card .val {
157
+ font-size: 26px;
158
+ font-weight: 700;
159
+ letter-spacing: -.022em;
160
+ color: #fff;
161
+ margin-top: 6px;
162
+ line-height: 1.05;
163
+ }
164
+ .summary-card .sub {
165
+ font-size: 11.5px;
166
+ color: var(--ink-muted);
167
+ margin-top: 2px;
168
+ }
169
+ .card-violations { --c: var(--rose); --c-soft: rgba(248,113,113,.10); }
170
+ .card-criteria { --c: var(--amber); --c-soft: rgba(251,191,36,.10); }
171
+ .card-pass { --c: var(--emerald); --c-soft: rgba(52,211,153,.10); }
172
+
173
+ .grid {
174
+ display: grid;
175
+ grid-template-columns: 1fr 1fr;
176
+ gap: 14px;
177
+ }
178
+ .panel {
179
+ background: var(--bg-1);
180
+ border: 1px solid var(--line);
181
+ border-radius: 12px;
182
+ padding: 16px;
183
+ box-shadow:
184
+ inset 0 1px 0 rgba(255,255,255,.03),
185
+ 0 2px 6px rgba(0,0,0,.18);
186
+ }
187
+ .panel.full-width { grid-column: 1 / -1; }
188
+ .panel-head {
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: space-between;
192
+ margin-bottom: 12px;
193
+ }
194
+ .panel-head h2 {
195
+ font-size: 11.5px;
196
+ font-weight: 600;
197
+ letter-spacing: .08em;
198
+ text-transform: uppercase;
199
+ color: var(--ink-muted);
200
+ display: flex;
201
+ align-items: center;
202
+ gap: 7px;
203
+ }
204
+ .panel-head h2 .dot {
205
+ width: 6px; height: 6px;
206
+ border-radius: 50%;
207
+ background: var(--rose);
208
+ box-shadow: 0 0 8px var(--rose);
209
+ }
210
+ .panel-head .pill {
211
+ font-size: 10.5px;
212
+ padding: 2px 8px;
213
+ border-radius: 999px;
214
+ background: var(--bg-3);
215
+ color: var(--ink-faint);
216
+ font-weight: 500;
217
+ }
218
+
219
+ .criterion {
220
+ font-size: 11px;
221
+ font-family: ui-monospace, monospace;
222
+ color: var(--ink-faint);
223
+ letter-spacing: -.01em;
224
+ }
225
+ .panel-desc {
226
+ font-size: 12.5px;
227
+ color: var(--ink-muted);
228
+ margin-bottom: 10px;
229
+ line-height: 1.5;
230
+ }
231
+
232
+ .badge {
233
+ padding: 2px 9px;
234
+ border-radius: 999px;
235
+ font-size: 10.5px;
236
+ font-weight: 700;
237
+ letter-spacing: .04em;
238
+ text-transform: uppercase;
239
+ }
240
+ .badge-fail { background: var(--rose-soft); color: var(--rose); border: 1px solid rgba(248,113,113,.3); }
241
+ .badge-pass { background: var(--emerald-soft); color: var(--emerald); border: 1px solid rgba(52,211,153,.3); }
242
+
243
+ .row {
244
+ display: flex;
245
+ justify-content: space-between;
246
+ align-items: center;
247
+ padding: 8px 0;
248
+ border-bottom: 1px solid var(--line-soft);
249
+ font-size: 12.5px;
250
+ }
251
+ .row:last-child { border-bottom: none; }
252
+ .row-label { color: var(--ink); }
253
+ .row-label .meta { color: var(--ink-faint); margin-left: 6px; font-size: 11.5px; }
254
+
255
+ /* Violation specimens */
256
+ .specimen-strip {
257
+ display: flex;
258
+ flex-wrap: wrap;
259
+ gap: 6px;
260
+ margin-top: 10px;
261
+ padding: 10px;
262
+ background: var(--bg-0);
263
+ border: 1px solid var(--line-soft);
264
+ border-radius: 8px;
265
+ }
266
+ .specimen-strip img {
267
+ display: block;
268
+ width: 56px; height: 38px;
269
+ border-radius: 5px;
270
+ border: 1px solid var(--line);
271
+ }
272
+ .specimen-strip input {
273
+ flex: 1;
274
+ min-width: 140px;
275
+ padding: 6px 9px;
276
+ border-radius: 6px;
277
+ border: 1px solid var(--line);
278
+ background: var(--bg-2);
279
+ color: var(--ink);
280
+ font: inherit;
281
+ font-size: 12px;
282
+ outline: none;
283
+ }
284
+ .specimen-strip input::placeholder { color: var(--ink-dim); }
285
+ .specimen-strip input:focus { border-color: var(--amber); box-shadow: 0 0 0 3px var(--amber-soft); }
286
+
287
+ /* Empty / unlabeled buttons (deliberate violations) */
288
+ .specimen-strip button.unlabeled {
289
+ width: 32px; height: 32px;
290
+ border-radius: 7px;
291
+ background: var(--bg-2);
292
+ border: 1px solid var(--line);
293
+ cursor: pointer;
294
+ }
295
+ .specimen-strip a.unlabeled {
296
+ width: 32px; height: 32px;
297
+ border-radius: 7px;
298
+ background: var(--bg-2);
299
+ border: 1px solid var(--line);
300
+ display: inline-block;
301
+ }
302
+
303
+ /* Submit report bar */
304
+ .submit-bar {
305
+ margin-top: 16px;
306
+ padding: 14px 16px;
307
+ background: var(--bg-1);
308
+ border: 1px solid var(--line);
309
+ border-radius: 12px;
310
+ display: flex;
311
+ align-items: center;
312
+ justify-content: space-between;
313
+ gap: 12px;
314
+ }
315
+ .submit-bar .left {
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 12px;
319
+ }
320
+ .submit-bar .icon-circle {
321
+ width: 36px; height: 36px;
322
+ border-radius: 10px;
323
+ background: var(--indigo-soft);
324
+ border: 1px solid rgba(129,140,248,.3);
325
+ display: grid;
326
+ place-items: center;
327
+ color: var(--indigo);
328
+ }
329
+ .submit-bar p {
330
+ font-size: 12.5px;
331
+ color: var(--ink-muted);
332
+ }
333
+ .submit-bar p strong { color: var(--ink); font-weight: 600; }
334
+
335
+ button {
336
+ font: inherit;
337
+ cursor: pointer;
338
+ transition: background .15s, border-color .15s, color .15s, transform .08s;
339
+ }
340
+ button:active:not([disabled]) { transform: translateY(1px); }
341
+ .btn {
342
+ padding: 7px 14px;
343
+ border-radius: 8px;
344
+ border: 1px solid var(--line);
345
+ background: var(--bg-2);
346
+ color: var(--ink);
347
+ font-size: 12.5px;
348
+ font-weight: 600;
349
+ }
350
+ .btn:hover { background: var(--bg-3); border-color: #2c3550; }
351
+ .btn-primary {
352
+ background: linear-gradient(180deg, var(--amber), var(--amber-strong));
353
+ color: #2a1a05;
354
+ border: none;
355
+ box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 4px 12px rgba(251,191,36,.2);
356
+ }
357
+ .btn-primary:hover { filter: brightness(1.05); }
358
+
359
+ .submit-success {
360
+ display: none;
361
+ margin-top: 12px;
362
+ padding: 11px 14px;
363
+ border-radius: 10px;
364
+ background: var(--emerald-soft);
365
+ border: 1px solid rgba(52,211,153,.3);
366
+ color: var(--emerald);
367
+ font-size: 13px;
368
+ font-weight: 500;
369
+ align-items: center;
370
+ gap: 8px;
371
+ }
372
+ .submit-success.show { display: flex; }
28
373
  </style>
29
374
  </head>
30
375
  <body>
31
- <h1>WCAG Accessibility Audit — Negative Test</h1>
32
- <p class="subtitle">This page is deliberately riddled with WCAG violations. Alethia's accessibility audit should catch all of them.</p>
33
376
 
34
- <div class="score-bar">
35
- <div><span class="score-label">Expected violations:</span> <span class="score-value score-bad">12+</span></div>
36
- <div><span class="score-label">If audit reports 0:</span> <span class="score-value score-bad">BUG</span></div>
37
- </div>
38
-
39
- <div class="grid">
40
- <div class="panel">
41
- <h2>Missing Alt Text (WCAG 1.1.1)</h2>
42
- <div class="violation">
43
- <span class="violation-label">Image without alt</span>
44
- <span class="expect-fail">SHOULD CATCH</span>
377
+ <div class="topbar">
378
+ <div class="brand">
379
+ <div class="brand-mark">
380
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="3"></circle></svg>
45
381
  </div>
46
- <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%234a6a9a'/><circle cx='16' cy='14' r='4' fill='%23ffd166'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%237eb8da'/></svg>" />
47
- <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%234a6a9a'/><circle cx='16' cy='14' r='4' fill='%23ffd166'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%237eb8da'/></svg>" />
48
- <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%234a6a9a'/><circle cx='16' cy='14' r='4' fill='%23ffd166'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%237eb8da'/></svg>" />
49
- </div>
50
-
51
- <div class="panel">
52
- <h2>Missing Form Labels (WCAG 1.3.1)</h2>
53
- <div class="violation">
54
- <span class="violation-label">Input without label or aria-label</span>
55
- <span class="expect-fail">SHOULD CATCH</span>
382
+ <div>
383
+ <div class="brand-name">WCAG Accessibility Audit</div>
384
+ <div class="brand-eyebrow">WCAG 2.1 AA · 14 criteria</div>
56
385
  </div>
57
- <input type="text" placeholder="No label" />
58
- <input type="email" placeholder="Also no label" />
59
- <input type="tel" placeholder="Phone with no label" />
60
386
  </div>
387
+ <span class="neg-badge">Negative Test Surface</span>
388
+ </div>
389
+
390
+ <div class="container">
61
391
 
62
- <div class="panel">
63
- <h2>Empty Buttons & Links (WCAG 4.1.2)</h2>
64
- <div class="violation">
65
- <span class="violation-label">Button with no text or aria-label</span>
66
- <span class="expect-fail">SHOULD CATCH</span>
392
+ <div class="lead-card">
393
+ <div class="lead-icon">
394
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3z"></path><path d="M12 9v4M12 17h.01"></path></svg>
67
395
  </div>
68
- <button id="empty-btn-1"></button>
69
- <button id="empty-btn-2"></button>
70
- <div class="violation">
71
- <span class="violation-label">Link with no text</span>
72
- <span class="expect-fail">SHOULD CATCH</span>
396
+ <div>
397
+ <h1>This page is deliberately riddled with WCAG violations.</h1>
398
+ <p>Alethia's accessibility audit should catch all of them. If the audit reports zero, that's a bug.</p>
73
399
  </div>
74
- <a href="#nowhere" id="empty-link-1"></a>
75
- <a href="#nowhere" id="empty-link-2"></a>
76
400
  </div>
77
401
 
78
- <div class="panel">
79
- <h2>Missing Page Metadata (WCAG 2.4.2)</h2>
80
- <div class="violation">
81
- <span class="violation-label">No &lt;html lang&gt; attribute</span>
82
- <span class="expect-fail">SHOULD CATCH</span>
402
+ <div class="summary">
403
+ <div class="summary-card card-violations">
404
+ <div class="head"><span class="dot"></span>Expected Violations</div>
405
+ <div class="val">12+</div>
406
+ <div class="sub">across 4 categories</div>
83
407
  </div>
84
- <div class="violation">
85
- <span class="violation-label">No &lt;title&gt; element</span>
86
- <span class="expect-fail">SHOULD CATCH</span>
408
+ <div class="summary-card card-criteria">
409
+ <div class="head"><span class="dot"></span>WCAG Criteria</div>
410
+ <div class="val">4</div>
411
+ <div class="sub">1.1.1 · 1.3.1 · 2.4.2 · 4.1.2</div>
87
412
  </div>
88
- <div class="violation">
89
- <span class="violation-label">Missing autocomplete on email/tel inputs</span>
90
- <span class="expect-fail">SHOULD CATCH</span>
413
+ <div class="summary-card card-pass">
414
+ <div class="head"><span class="dot"></span>Compliant Specimens</div>
415
+ <div class="val">3</div>
416
+ <div class="sub">should not be flagged</div>
91
417
  </div>
92
418
  </div>
93
419
 
94
- <div class="panel full-width">
95
- <h2>Correctly Accessible Elements (should NOT be flagged)</h2>
96
- <div class="violation">
97
- <span class="violation-label">Image with alt text</span>
98
- <span style="font-size:0.7rem;padding:0.15rem 0.5rem;border-radius:3px;background:#001a0a;color:#00cc66;border:1px solid #00cc66">SHOULD PASS</span>
420
+ <div class="grid">
421
+ <div class="panel">
422
+ <div class="panel-head">
423
+ <h2><span class="dot"></span>Missing Alt Text</h2>
424
+ <span class="pill">3 violations</span>
425
+ </div>
426
+ <div class="row">
427
+ <span class="row-label">Images without <code style="font-family: ui-monospace, monospace; color: var(--rose); font-size: 11.5px;">alt</code> attribute<span class="meta">WCAG 1.1.1 · A</span></span>
428
+ <span class="badge badge-fail">Should catch</span>
429
+ </div>
430
+ <p class="panel-desc" style="margin-top: 10px; margin-bottom: 4px;">Three decorative-but-untagged images below.</p>
431
+ <div class="specimen-strip">
432
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%2326304a'/><circle cx='16' cy='14' r='4' fill='%23fbbf24'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%2367e8f9'/></svg>" />
433
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%2326304a'/><circle cx='44' cy='12' r='5' fill='%23a78bfa'/><polygon points='4,30 18,20 30,28 42,18 56,32' fill='%23f472b6'/></svg>" />
434
+ <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%2326304a'/><circle cx='30' cy='14' r='4' fill='%2334d399'/><polygon points='2,32 16,16 28,28 40,12 58,32' fill='%2360a5fa'/></svg>" />
435
+ </div>
99
436
  </div>
100
- <img src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%234a6a9a'/><circle cx='16' cy='14' r='4' fill='%23ffd166'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%237eb8da'/></svg>" alt="Decorative placeholder" />
101
- <div class="violation">
102
- <span class="violation-label">Input with aria-label</span>
103
- <span style="font-size:0.7rem;padding:0.15rem 0.5rem;border-radius:3px;background:#001a0a;color:#00cc66;border:1px solid #00cc66">SHOULD PASS</span>
437
+
438
+ <div class="panel">
439
+ <div class="panel-head">
440
+ <h2><span class="dot"></span>Missing Form Labels</h2>
441
+ <span class="pill">3 violations</span>
442
+ </div>
443
+ <div class="row">
444
+ <span class="row-label">Inputs without <code style="font-family: ui-monospace, monospace; color: var(--rose); font-size: 11.5px;">&lt;label&gt;</code> or aria-label<span class="meta">WCAG 1.3.1 · A</span></span>
445
+ <span class="badge badge-fail">Should catch</span>
446
+ </div>
447
+ <p class="panel-desc" style="margin-top: 10px; margin-bottom: 4px;">Placeholder-only inputs are insufficient for screen readers.</p>
448
+ <div class="specimen-strip">
449
+ <input type="text" placeholder="No label" />
450
+ <input type="email" placeholder="Also no label" />
451
+ <input type="tel" placeholder="Phone with no label" />
452
+ </div>
104
453
  </div>
105
- <input type="text" aria-label="Search" placeholder="Search..." />
106
- <div class="violation">
107
- <span class="violation-label">Button with text</span>
108
- <span style="font-size:0.7rem;padding:0.15rem 0.5rem;border-radius:3px;background:#001a0a;color:#00cc66;border:1px solid #00cc66">SHOULD PASS</span>
454
+
455
+ <div class="panel">
456
+ <div class="panel-head">
457
+ <h2><span class="dot"></span>Empty Buttons &amp; Links</h2>
458
+ <span class="pill">4 violations</span>
459
+ </div>
460
+ <div class="row">
461
+ <span class="row-label">Buttons / links with no accessible name<span class="meta">WCAG 4.1.2 · A</span></span>
462
+ <span class="badge badge-fail">Should catch</span>
463
+ </div>
464
+ <p class="panel-desc" style="margin-top: 10px; margin-bottom: 4px;">Two empty buttons, two empty anchor tags.</p>
465
+ <div class="specimen-strip">
466
+ <button class="unlabeled" id="empty-btn-1"></button>
467
+ <button class="unlabeled" id="empty-btn-2"></button>
468
+ <a class="unlabeled" href="#nowhere" id="empty-link-1"></a>
469
+ <a class="unlabeled" href="#nowhere" id="empty-link-2"></a>
470
+ </div>
471
+ </div>
472
+
473
+ <div class="panel">
474
+ <div class="panel-head">
475
+ <h2><span class="dot"></span>Missing Page Metadata</h2>
476
+ <span class="pill">3 violations</span>
477
+ </div>
478
+ <div class="row"><span class="row-label">No <code style="font-family: ui-monospace, monospace; color: var(--rose); font-size: 11.5px;">&lt;html lang&gt;</code><span class="meta">WCAG 3.1.1 · A</span></span><span class="badge badge-fail">Should catch</span></div>
479
+ <div class="row"><span class="row-label">No <code style="font-family: ui-monospace, monospace; color: var(--rose); font-size: 11.5px;">&lt;title&gt;</code><span class="meta">WCAG 2.4.2 · A</span></span><span class="badge badge-fail">Should catch</span></div>
480
+ <div class="row"><span class="row-label">Missing <code style="font-family: ui-monospace, monospace; color: var(--rose); font-size: 11.5px;">autocomplete</code> on email/tel<span class="meta">WCAG 1.3.5 · AA</span></span><span class="badge badge-fail">Should catch</span></div>
481
+ </div>
482
+
483
+ <div class="panel full-width">
484
+ <div class="panel-head">
485
+ <h2><span class="dot" style="background: var(--emerald); box-shadow: 0 0 8px var(--emerald);"></span>Compliant Specimens</h2>
486
+ <span class="pill">should not be flagged</span>
487
+ </div>
488
+ <div class="row"><span class="row-label">Image with descriptive alt text</span><span class="badge badge-pass">Should pass</span></div>
489
+ <div class="specimen-strip">
490
+ <img alt="Decorative chart placeholder showing data trend"
491
+ src="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 40'><rect width='60' height='40' fill='%2326304a'/><polygon points='6,32 22,18 32,26 44,14 54,32' fill='%2334d399'/></svg>" />
492
+ </div>
493
+ <div class="row"><span class="row-label">Input with <code style="font-family: ui-monospace, monospace; color: var(--emerald); font-size: 11.5px;">aria-label</code></span><span class="badge badge-pass">Should pass</span></div>
494
+ <div class="specimen-strip">
495
+ <input type="text" aria-label="Search compliance findings" placeholder="Search…" />
496
+ </div>
497
+ <div class="row"><span class="row-label">Button with visible text content</span><span class="badge badge-pass">Should pass</span></div>
498
+ <div class="submit-bar">
499
+ <div class="left">
500
+ <div class="icon-circle">
501
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>
502
+ </div>
503
+ <p><strong>Submit audit report.</strong> Generates the WCAG findings packet for review.</p>
504
+ </div>
505
+ <button class="btn btn-primary" id="submit-report-btn">Submit Report</button>
506
+ </div>
507
+ <div class="submit-success" id="submit-success">
508
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><path d="m9 11 3 3L22 4"></path></svg>
509
+ <span>Report submitted — findings packet generated and queued for review.</span>
510
+ </div>
109
511
  </div>
110
- <button>Submit Report</button>
111
512
  </div>
112
513
  </div>
514
+
515
+ <script>
516
+ document.getElementById('submit-report-btn').addEventListener('click', function () {
517
+ var ok = document.getElementById('submit-success');
518
+ ok.classList.add('show');
519
+ this.textContent = 'Submitted';
520
+ this.disabled = true;
521
+ this.style.filter = 'grayscale(.4) brightness(.95)';
522
+ });
523
+ </script>
113
524
  </body>
114
525
  </html>
package/dist/index.js CHANGED
@@ -1010,6 +1010,27 @@ class AlethiaConnectionError extends Error {
1010
1010
  this.name = 'AlethiaConnectionError';
1011
1011
  }
1012
1012
  }
1013
+ // Tell the runtime about a local helper-server port (e.g. the bridge's demo
1014
+ // server) so the cockpit's discovery dropdown can include it. Best-effort:
1015
+ // silently swallows errors so a missing/unupgraded runtime doesn't break
1016
+ // alethia_serve_demo for users.
1017
+ const registerForeignTargetWithRuntime = (port, label) => new Promise((resolveReg) => {
1018
+ const payload = JSON.stringify({ port, label });
1019
+ const req = http.request({
1020
+ hostname: ALETHIA_HOST,
1021
+ port: ALETHIA_PORT,
1022
+ path: '/_alethia/foreign-target/register',
1023
+ method: 'POST',
1024
+ headers: {
1025
+ 'content-type': 'application/json',
1026
+ 'content-length': Buffer.byteLength(payload),
1027
+ },
1028
+ }, (res) => { res.resume(); res.on('end', () => resolveReg()); res.on('error', () => resolveReg()); });
1029
+ req.setTimeout(2000, () => { req.destroy(); resolveReg(); });
1030
+ req.on('error', () => resolveReg());
1031
+ req.write(payload);
1032
+ req.end();
1033
+ });
1013
1034
  const callAlethia = (body, timeoutMs = ALETHIA_TIMEOUT_MS) => new Promise((resolveCall, rejectCall) => {
1014
1035
  const payload = JSON.stringify(body);
1015
1036
  debug('->', payload);
@@ -1638,6 +1659,12 @@ const handle = async (request) => {
1638
1659
  if (toolName === 'alethia_serve_demo') {
1639
1660
  try {
1640
1661
  const { port, url, pages } = await startDemoServer();
1662
+ // Register the ephemeral port with the runtime so it appears in the
1663
+ // cockpit's "Rescan / Targets" dropdown. Best-effort — if the
1664
+ // runtime isn't up yet, the demo still works via the URL we return,
1665
+ // it just won't auto-discover. Idempotent: re-registering the same
1666
+ // port is a no-op on the runtime side.
1667
+ void registerForeignTargetWithRuntime(port, 'Alethia Demo Server (bridge)');
1641
1668
  const pageList = pages.map(p => ` ${url}/${p}`).join('\n');
1642
1669
  return {
1643
1670
  jsonrpc: '2.0',