@geotechcli/core 0.4.39 → 0.4.41

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.
@@ -31,15 +31,15 @@ function toneClass(tone) {
31
31
  function toneColor(tone) {
32
32
  switch (tone) {
33
33
  case 'good':
34
- return '#d9f5e8';
34
+ return '#0f3d2e';
35
35
  case 'warning':
36
- return '#f7e4bd';
36
+ return '#5f3b0b';
37
37
  case 'danger':
38
- return '#f9d3cf';
38
+ return '#5f1717';
39
39
  case 'accent':
40
- return '#d8e9f7';
40
+ return '#0b3b56';
41
41
  default:
42
- return '#e8edf3';
42
+ return '#1f2937';
43
43
  }
44
44
  }
45
45
  function parsePercent(value) {
@@ -68,6 +68,15 @@ function sourceModeLabel(sourceHint) {
68
68
  return 'Extraction evidence retained';
69
69
  }
70
70
  }
71
+ function reviewFilterValue(item) {
72
+ if (/not extracted|missing/i.test(item.value) || /required/i.test(item.review)) {
73
+ return 'missing';
74
+ }
75
+ if (/recommended|verify|manual|visual|review/i.test(item.review) && !/ready/i.test(item.review)) {
76
+ return 'needs_review';
77
+ }
78
+ return 'verified';
79
+ }
71
80
  function compactSvgText(value, maxLength = 28) {
72
81
  const normalized = (value ?? '').replace(/\s+/g, ' ').trim();
73
82
  if (normalized.length <= maxLength) {
@@ -90,6 +99,21 @@ function renderMetric(metric) {
90
99
  </article>
91
100
  `;
92
101
  }
102
+ function renderStatusBadges(dossier) {
103
+ if (dossier.badges.length === 0) {
104
+ return '';
105
+ }
106
+ return `
107
+ <div class="status-badge-row" aria-label="Document status">
108
+ ${dossier.badges.map((badge) => `
109
+ <span class="status-badge ${toneClass(badge.tone)}">
110
+ <span>${escapeHtml(badge.label)}</span>
111
+ <strong>${escapeHtml(badge.value)}</strong>
112
+ </span>
113
+ `).join('')}
114
+ </div>
115
+ `;
116
+ }
93
117
  function renderTable(table, className = 'data-section') {
94
118
  const tableId = escapeHtml(idFromTitle(table.title));
95
119
  const body = table.rows.length === 0
@@ -119,8 +143,18 @@ function renderTable(table, className = 'data-section') {
119
143
  function renderTrustTable(dossier) {
120
144
  const rows = dossier.trustItems.length === 0
121
145
  ? '<tr><td colspan="6">No trust-layer rows were retained.</td></tr>'
122
- : dossier.trustItems.map((item) => `
123
- <tr>
146
+ : dossier.trustItems.map((item) => {
147
+ const reviewFilter = reviewFilterValue(item);
148
+ const searchText = [
149
+ item.item,
150
+ item.value,
151
+ item.sourcePage,
152
+ item.confidence,
153
+ item.review,
154
+ item.evidence,
155
+ ].join(' ').toLowerCase();
156
+ return `
157
+ <tr data-trust-row data-review="${escapeHtml(reviewFilter)}" data-search="${escapeHtml(searchText)}">
124
158
  <td><strong>${escapeHtml(item.item)}</strong></td>
125
159
  <td>${escapeHtml(item.value)}</td>
126
160
  <td>${escapeHtml(item.sourcePage)}</td>
@@ -128,13 +162,26 @@ function renderTrustTable(dossier) {
128
162
  <td>${escapeHtml(item.review)}</td>
129
163
  <td>${escapeHtml(item.evidence)}</td>
130
164
  </tr>
131
- `).join('');
165
+ `;
166
+ }).join('');
132
167
  return `
133
168
  <section class="data-section" id="parameters">
134
169
  <div class="section-heading">
135
170
  <h2>Engineering Parameters</h2>
136
171
  <p>Evidence-first review table. Every retained or missing item is shown with source, confidence, review posture, and an evidence snippet.</p>
137
172
  </div>
173
+ <div class="trust-controls" aria-label="Parameter table controls">
174
+ <label>
175
+ <span>Search parameters</span>
176
+ <input id="trust-search" type="search" placeholder="Filter by parameter, value, source, or evidence" />
177
+ </label>
178
+ <div class="filter-row" aria-label="Review status filters">
179
+ <button type="button" class="filter-button active" data-trust-filter="all">All</button>
180
+ <button type="button" class="filter-button" data-trust-filter="needs_review">Needs review</button>
181
+ <button type="button" class="filter-button" data-trust-filter="missing">Missing</button>
182
+ <button type="button" class="filter-button" data-trust-filter="verified">Verified</button>
183
+ </div>
184
+ </div>
138
185
  <div class="table-shell trust-table">
139
186
  <table>
140
187
  <thead>
@@ -153,6 +200,97 @@ function renderTrustTable(dossier) {
153
200
  </section>
154
201
  `;
155
202
  }
203
+ function renderGroundModelCrossSection(profile) {
204
+ if (!profile || profile.columns.length < 2 || profile.maxDepth <= 0) {
205
+ return `
206
+ <section class="data-section" id="ground-cross-section">
207
+ <div class="section-heading">
208
+ <h2>Ground Model Cross-Section</h2>
209
+ <p>A schematic cross-section needs at least two boreholes with retained depth evidence.</p>
210
+ </div>
211
+ <div class="empty-state">Ground-model bands were not drawn because borehole depth and layer evidence were incomplete.</div>
212
+ </section>
213
+ `;
214
+ }
215
+ const width = 940;
216
+ const height = 280;
217
+ const plotTop = 52;
218
+ const plotHeight = 158;
219
+ const left = 84;
220
+ const right = width - 44;
221
+ const usableWidth = right - left;
222
+ const referenceLayers = profile.columns
223
+ .flatMap((column) => column.layers)
224
+ .sort((leftLayer, rightLayer) => leftLayer.depthFrom - rightLayer.depthFrom)
225
+ .slice(0, 8);
226
+ const layers = referenceLayers.length > 0
227
+ ? referenceLayers
228
+ : [{
229
+ depthFrom: 0,
230
+ depthTo: profile.maxDepth,
231
+ label: 'Ground profile',
232
+ description: 'Layer boundaries were not available in structured form.',
233
+ tone: 'neutral',
234
+ uncertain: true,
235
+ }];
236
+ const yForDepth = (depth) => plotTop + (Math.max(0, Math.min(profile.maxDepth, depth)) / profile.maxDepth) * plotHeight;
237
+ const ticks = Array.from({ length: 6 }, (_value, index) => Number((profile.maxDepth * index / 5).toFixed(2)));
238
+ const columnX = (index) => profile.columns.length === 1
239
+ ? left + usableWidth / 2
240
+ : left + (usableWidth * index / (profile.columns.length - 1));
241
+ return `
242
+ <section class="data-section" id="ground-cross-section">
243
+ <div class="section-heading">
244
+ <h2>Ground Model Cross-Section</h2>
245
+ <p>Conceptual cross-section connecting retained borehole evidence. Bands are schematic and should be verified against source logs before design use.</p>
246
+ </div>
247
+ <div class="profile-shell cross-section-shell">
248
+ <svg class="ground-cross-section" viewBox="0 0 ${width} ${height}" role="img" aria-label="AI-assisted ground model cross-section">
249
+ <rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="#0f172a" />
250
+ ${ticks.map((tick) => {
251
+ const y = yForDepth(tick);
252
+ return `
253
+ <line x1="${left - 34}" y1="${y.toFixed(2)}" x2="${right + 12}" y2="${y.toFixed(2)}" stroke="#334155" stroke-width="1" />
254
+ <text x="14" y="${(y + 4).toFixed(2)}" class="profile-axis">${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))} ${escapeHtml(profile.depthUnit)}</text>
255
+ `;
256
+ }).join('')}
257
+ ${layers.map((layer) => {
258
+ const y1 = yForDepth(layer.depthFrom);
259
+ const y2 = Math.max(y1 + 18, yForDepth(layer.depthTo));
260
+ return `
261
+ <g>
262
+ <title>${escapeHtml(`${layer.label}: ${layer.description}`)}</title>
263
+ <path d="M ${left} ${y1.toFixed(2)} L ${right} ${y1.toFixed(2)} L ${right} ${y2.toFixed(2)} L ${left} ${y2.toFixed(2)} Z"
264
+ fill="${toneColor(layer.tone)}" stroke="#64748b" stroke-width="1.2" ${layer.uncertain ? 'stroke-dasharray="8 6"' : ''} opacity="0.92" />
265
+ <text x="${left + 18}" y="${(y1 + Math.min(34, (y2 - y1) / 2 + 5)).toFixed(2)}" class="profile-label">${escapeHtml(compactSvgText(layer.label, 32))}</text>
266
+ <text x="${left + 18}" y="${(y1 + Math.min(52, (y2 - y1) / 2 + 23)).toFixed(2)}" class="profile-small">${escapeHtml(compactSvgText(layer.description, 64))}</text>
267
+ </g>
268
+ `;
269
+ }).join('')}
270
+ ${profile.columns.map((column, index) => {
271
+ const x = columnX(index);
272
+ const depth = column.totalDepth ?? profile.maxDepth;
273
+ const bottomY = yForDepth(depth);
274
+ const waterY = column.waterTableDepth != null ? yForDepth(column.waterTableDepth) : null;
275
+ return `
276
+ <g>
277
+ <line x1="${x.toFixed(2)}" y1="${plotTop - 10}" x2="${x.toFixed(2)}" y2="${bottomY.toFixed(2)}" stroke="#06b6d4" stroke-width="3" />
278
+ <circle cx="${x.toFixed(2)}" cy="${plotTop - 12}" r="6" fill="#06b6d4" />
279
+ <text x="${(x - 24).toFixed(2)}" y="28" class="profile-title">${escapeHtml(column.boreholeId)}</text>
280
+ <text x="${(x - 36).toFixed(2)}" y="${height - 32}" class="profile-small">TD ${escapeHtml(depth.toFixed(2))} ${escapeHtml(profile.depthUnit)}</text>
281
+ ${waterY != null ? `
282
+ <line x1="${(x - 34).toFixed(2)}" y1="${waterY.toFixed(2)}" x2="${(x + 34).toFixed(2)}" y2="${waterY.toFixed(2)}" stroke="#0891b2" stroke-width="2" />
283
+ <text x="${(x - 40).toFixed(2)}" y="${(waterY - 7).toFixed(2)}" class="profile-water">GW</text>
284
+ ` : ''}
285
+ </g>
286
+ `;
287
+ }).join('')}
288
+ </svg>
289
+ </div>
290
+ <p class="verification-note">AI-assisted ground model. Use source logs and engineering judgment before adopting layer continuity, groundwater, or design parameters.</p>
291
+ </section>
292
+ `;
293
+ }
156
294
  function renderBoreholeProfile(profile) {
157
295
  if (!profile || profile.columns.length === 0 || profile.maxDepth <= 0) {
158
296
  return `
@@ -166,9 +304,9 @@ function renderBoreholeProfile(profile) {
166
304
  `;
167
305
  }
168
306
  const plotTop = 44;
169
- const plotHeight = 420;
170
- const columnWidth = 132;
171
- const gap = 42;
307
+ const plotHeight = 330;
308
+ const columnWidth = 110;
309
+ const gap = 32;
172
310
  const axisWidth = 78;
173
311
  const width = axisWidth + profile.columns.length * columnWidth + Math.max(0, profile.columns.length - 1) * gap + 42;
174
312
  const height = plotTop + plotHeight + 74;
@@ -196,11 +334,11 @@ function renderBoreholeProfile(profile) {
196
334
  </div>
197
335
  <div class="profile-shell">
198
336
  <svg class="borehole-profile" viewBox="0 0 ${width} ${height}" role="img" aria-label="Borehole stratigraphy profile">
199
- <rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="#ffffff" />
337
+ <rect x="0" y="0" width="${width}" height="${height}" rx="18" fill="#0f172a" />
200
338
  ${ticks.map((tick) => {
201
339
  const y = plotTop + (tick / profile.maxDepth) * plotHeight;
202
340
  return `
203
- <line x1="48" y1="${y.toFixed(2)}" x2="${width - 24}" y2="${y.toFixed(2)}" stroke="#d8e0ea" stroke-width="1" />
341
+ <line x1="48" y1="${y.toFixed(2)}" x2="${width - 24}" y2="${y.toFixed(2)}" stroke="#334155" stroke-width="1" />
204
342
  <text x="8" y="${(y + 4).toFixed(2)}" class="profile-axis">${escapeHtml(tick.toFixed(tick % 1 === 0 ? 0 : 1))} ${escapeHtml(profile.depthUnit)}</text>
205
343
  `;
206
344
  }).join('')}
@@ -213,7 +351,7 @@ function renderBoreholeProfile(profile) {
213
351
  <text x="${x}" y="24" class="profile-title">${escapeHtml(column.boreholeId)}</text>
214
352
  ${column.layers.map((layer) => layerRect(layer, columnIndex)).join('')}
215
353
  ${waterY != null ? `
216
- <line x1="${x - 8}" y1="${waterY.toFixed(2)}" x2="${x + columnWidth + 8}" y2="${waterY.toFixed(2)}" stroke="#0b4f8a" stroke-width="2" />
354
+ <line x1="${x - 8}" y1="${waterY.toFixed(2)}" x2="${x + columnWidth + 8}" y2="${waterY.toFixed(2)}" stroke="#06b6d4" stroke-width="2" />
217
355
  <text x="${x + 8}" y="${(waterY - 6).toFixed(2)}" class="profile-water">Groundwater</text>
218
356
  ` : ''}
219
357
  <text x="${x}" y="${height - 24}" class="profile-small">TD ${escapeHtml(column.totalDepth != null ? `${column.totalDepth.toFixed(2)} m` : 'unavailable')}</text>
@@ -278,6 +416,11 @@ function renderSourceEvidence(dossier) {
278
416
  ].filter(Boolean).join(' | '))}</p>
279
417
  ${card.highlights.length > 0 ? `<ul>${card.highlights.map((item) => `<li>${escapeHtml(item)}</li>`).join('')}</ul>` : '<p>No strong structured highlight was retained for this page.</p>'}
280
418
  ${card.warnings.length > 0 ? `<small>Human verification recommended: ${escapeHtml(String(card.warnings.length))} retained warning(s).</small>` : ''}
419
+ <div class="evidence-actions" aria-label="${escapeHtml(`${card.pageLabel} review actions`)}">
420
+ <a href="#processing-audit">Open source page</a>
421
+ <button type="button" data-review-action="verified">Mark verified</button>
422
+ <button type="button" data-review-action="flagged">Flag issue</button>
423
+ </div>
281
424
  </article>
282
425
  `).join('')}
283
426
  </div>
@@ -336,6 +479,28 @@ function renderProcessingAudit(dossier, auditTables) {
336
479
  </details>
337
480
  `;
338
481
  }
482
+ function renderReviewBar(dossier) {
483
+ const missingCount = dossier.trustItems.filter((item) => reviewFilterValue(item) === 'missing').length;
484
+ const needsReviewCount = dossier.trustItems.filter((item) => reviewFilterValue(item) === 'needs_review').length;
485
+ const reviewBadgeValue = dossier.badges.find((badge) => /review/i.test(badge.label))?.value ?? 'Yes';
486
+ const reviewRequired = /^no$/i.test(reviewBadgeValue) ? 'Review ready' : 'Review required';
487
+ return `
488
+ <div class="review-bar" role="region" aria-label="Human review workflow">
489
+ <div>
490
+ <strong>${escapeHtml(reviewRequired)}</strong>
491
+ <span>${escapeHtml(`${dossier.pageCards.length} page(s), ${dossier.trustItems.length} review item(s), ${needsReviewCount} need review, ${missingCount} missing`)}</span>
492
+ </div>
493
+ <div class="review-actions">
494
+ <a href="#parameters" class="primary-action">Approve Extraction</a>
495
+ <a href="#risks">Flag for Review</a>
496
+ <button type="button" onclick="window.print()">Export Report</button>
497
+ <a href="#ground-cross-section">Generate Ground Model</a>
498
+ <a href="#source-evidence">Ask Geotech Agent</a>
499
+ <a href="#processing-audit">Open Audit Trail</a>
500
+ </div>
501
+ </div>
502
+ `;
503
+ }
339
504
  function renderActionBar() {
340
505
  return `
341
506
  <div class="action-bar" aria-label="Review actions">
@@ -364,32 +529,37 @@ export function renderIngestDossierAsHtml(dossier) {
364
529
  <head>
365
530
  <meta charset="utf-8" />
366
531
  <meta name="viewport" content="width=device-width, initial-scale=1" />
367
- <title>Geotechnical Intelligence Dossier - ${escapeHtml(dossier.title)}</title>
532
+ <title>Geotechnical Intelligence Report - ${escapeHtml(dossier.title)}</title>
368
533
  <style>
369
534
  :root {
370
- --bg: #f6f8fb;
371
- --surface: #ffffff;
372
- --surface-soft: #eef3f8;
373
- --text: #101828;
374
- --muted: #667085;
375
- --primary: #0b4f8a;
376
- --primary-soft: #e6f0fa;
377
- --success: #16875d;
378
- --success-soft: #e4f6ee;
379
- --warning: #b7791f;
380
- --warning-soft: #fff3d6;
381
- --danger: #b42318;
382
- --danger-soft: #fde5e2;
383
- --neutral: #475467;
384
- --neutral-soft: #edf1f6;
385
- --border: #d8e0ea;
386
- --shadow: 0 18px 45px rgba(16, 24, 40, 0.08);
387
- --radius: 16px;
388
- --radius-sm: 12px;
535
+ --bg: #060d1b;
536
+ --surface: #0f172a;
537
+ --surface-soft: #111827;
538
+ --surface-raised: #0b1120;
539
+ --text: #f8fafc;
540
+ --muted: #94a3b8;
541
+ --primary: #06b6d4;
542
+ --primary-soft: rgba(6, 182, 212, 0.12);
543
+ --success: #10b981;
544
+ --success-soft: rgba(16, 185, 129, 0.13);
545
+ --warning: #f59e0b;
546
+ --warning-soft: rgba(245, 158, 11, 0.13);
547
+ --danger: #ef4444;
548
+ --danger-soft: rgba(239, 68, 68, 0.13);
549
+ --neutral: #cbd5e1;
550
+ --neutral-soft: rgba(100, 116, 139, 0.15);
551
+ --border: #1e293b;
552
+ --border-strong: #334155;
553
+ --shadow: 0 20px 60px rgba(0, 0, 0, 0.28);
554
+ --radius: 14px;
555
+ --radius-sm: 10px;
389
556
  }
390
557
 
391
558
  * { box-sizing: border-box; }
392
- html { scroll-behavior: smooth; }
559
+ html {
560
+ scroll-behavior: smooth;
561
+ overflow-x: hidden;
562
+ }
393
563
  html, body { margin: 0; min-height: 100%; }
394
564
 
395
565
  body {
@@ -398,80 +568,105 @@ export function renderIngestDossierAsHtml(dossier) {
398
568
  background: var(--bg);
399
569
  font-variant-numeric: tabular-nums;
400
570
  overflow-x: hidden;
571
+ padding-bottom: 98px;
401
572
  }
402
573
 
403
574
  .layout {
404
575
  display: grid;
405
- grid-template-columns: 280px minmax(0, 1fr);
406
- gap: 28px;
576
+ grid-template-columns: minmax(0, 1fr);
577
+ gap: 24px;
407
578
  width: 100%;
408
- max-width: 1580px;
579
+ max-width: 1280px;
409
580
  margin: 0 auto;
410
- padding: 28px;
581
+ padding: 16px 24px 120px;
411
582
  min-width: 0;
412
583
  }
413
584
 
414
585
  .sidebar {
415
586
  position: sticky;
416
- top: 28px;
417
- align-self: start;
418
- min-height: calc(100vh - 56px);
587
+ top: 0;
588
+ z-index: 30;
589
+ display: flex;
590
+ align-items: center;
591
+ justify-content: space-between;
592
+ gap: 18px;
593
+ align-self: stretch;
594
+ min-height: 58px;
419
595
  min-width: 0;
420
- padding: 22px;
596
+ padding: 12px 16px;
421
597
  border: 1px solid var(--border);
422
- border-radius: var(--radius);
423
- background: #0b1220;
598
+ border-radius: 14px;
599
+ background: rgba(11, 17, 32, 0.94);
424
600
  color: #f8fafc;
425
- box-shadow: var(--shadow);
601
+ box-shadow: 0 14px 44px rgba(0, 0, 0, 0.26);
602
+ backdrop-filter: blur(14px);
426
603
  }
427
604
 
428
605
  .brand {
429
- display: grid;
430
- gap: 8px;
431
- padding-bottom: 22px;
432
- border-bottom: 1px solid #263244;
433
- margin-bottom: 18px;
606
+ display: flex;
607
+ align-items: center;
608
+ gap: 10px;
609
+ padding: 0;
610
+ border-bottom: 0;
611
+ margin: 0;
612
+ min-width: 190px;
613
+ }
614
+
615
+ .brand::before {
616
+ content: "";
617
+ width: 28px;
618
+ height: 28px;
619
+ border-radius: 8px;
620
+ background: var(--primary);
621
+ box-shadow: 0 0 24px rgba(6, 182, 212, 0.28);
622
+ flex: 0 0 auto;
434
623
  }
435
624
 
436
- .brand strong { font-size: 1.05rem; }
437
- .brand span { color: #94a3b8; line-height: 1.5; font-size: 0.9rem; }
625
+ .brand strong { font-size: 0.96rem; white-space: nowrap; }
626
+ .brand span { display: none; }
438
627
 
439
628
  .nav-list {
440
- display: grid;
629
+ display: flex;
630
+ align-items: center;
631
+ justify-content: flex-end;
441
632
  gap: 6px;
633
+ flex-wrap: wrap;
442
634
  }
443
635
 
444
636
  .nav-list a {
445
- color: #dbeafe;
637
+ color: #cbd5e1;
446
638
  text-decoration: none;
447
- padding: 10px 12px;
448
- border-radius: 10px;
639
+ padding: 8px 10px;
640
+ border-radius: 8px;
449
641
  font-weight: 650;
450
- font-size: 0.94rem;
642
+ font-size: 0.8rem;
451
643
  overflow-wrap: anywhere;
452
644
  }
453
645
 
454
646
  .nav-list a:hover, .nav-list a:focus {
455
- background: #1f2937;
647
+ background: rgba(100, 116, 139, 0.16);
456
648
  outline: none;
457
649
  }
458
650
 
459
651
  .content {
460
652
  display: grid;
461
- gap: 26px;
653
+ gap: 28px;
462
654
  min-width: 0;
463
655
  max-width: 100%;
464
656
  }
465
657
 
466
658
  .hero {
467
- padding: 32px;
659
+ padding: 30px;
468
660
  border: 1px solid var(--border);
469
661
  border-radius: 18px;
470
- background: linear-gradient(135deg, #ffffff 0%, #eef6ff 100%);
662
+ background:
663
+ radial-gradient(circle at top right, rgba(6, 182, 212, 0.09), transparent 34%),
664
+ linear-gradient(135deg, #0b1120 0%, #0f172a 100%);
471
665
  box-shadow: var(--shadow);
472
666
  display: grid;
473
- gap: 24px;
667
+ gap: 22px;
474
668
  min-width: 0;
669
+ overflow: hidden;
475
670
  }
476
671
 
477
672
  .eyebrow {
@@ -480,7 +675,7 @@ export function renderIngestDossierAsHtml(dossier) {
480
675
  padding: 8px 12px;
481
676
  border-radius: 999px;
482
677
  background: var(--primary-soft);
483
- color: var(--primary);
678
+ color: #67e8f9;
484
679
  font-weight: 800;
485
680
  font-size: 0.78rem;
486
681
  text-transform: uppercase;
@@ -488,7 +683,7 @@ export function renderIngestDossierAsHtml(dossier) {
488
683
 
489
684
  .hero-main {
490
685
  display: grid;
491
- grid-template-columns: minmax(0, 1fr) 250px;
686
+ grid-template-columns: minmax(0, 1fr) 300px;
492
687
  gap: 24px;
493
688
  align-items: start;
494
689
  min-width: 0;
@@ -510,7 +705,7 @@ export function renderIngestDossierAsHtml(dossier) {
510
705
 
511
706
  .hero-subtitle {
512
707
  margin-top: 12px;
513
- color: var(--primary);
708
+ color: #67e8f9;
514
709
  font-weight: 800;
515
710
  font-size: 1.12rem;
516
711
  }
@@ -527,16 +722,51 @@ export function renderIngestDossierAsHtml(dossier) {
527
722
  display: grid;
528
723
  gap: 10px;
529
724
  padding: 18px;
530
- border: 1px solid var(--border);
725
+ border: 1px solid var(--border-strong);
531
726
  border-radius: var(--radius-sm);
532
- background: rgba(255, 255, 255, 0.72);
727
+ background: rgba(15, 23, 42, 0.74);
533
728
  color: var(--muted);
534
729
  font-size: 0.92rem;
535
730
  min-width: 0;
536
731
  overflow-wrap: anywhere;
537
732
  }
538
733
 
539
- .hero-meta strong { color: var(--text); }
734
+ .hero-meta strong {
735
+ color: var(--text);
736
+ overflow-wrap: anywhere;
737
+ }
738
+
739
+ .status-badge-row {
740
+ display: flex;
741
+ flex-wrap: wrap;
742
+ gap: 9px;
743
+ margin-top: 16px;
744
+ }
745
+
746
+ .status-badge {
747
+ display: inline-grid;
748
+ gap: 4px;
749
+ min-width: 126px;
750
+ padding: 10px 12px;
751
+ border: 1px solid currentColor;
752
+ border-radius: 999px;
753
+ line-height: 1.2;
754
+ background: rgba(15, 23, 42, 0.74);
755
+ }
756
+
757
+ .status-badge span {
758
+ color: currentColor;
759
+ opacity: 0.72;
760
+ font-size: 0.68rem;
761
+ text-transform: uppercase;
762
+ font-weight: 900;
763
+ }
764
+
765
+ .status-badge strong {
766
+ color: currentColor;
767
+ font-size: 0.86rem;
768
+ overflow-wrap: anywhere;
769
+ }
540
770
 
541
771
  .executive-grid, .metric-grid, .card-grid, .evidence-grid, .audit-page-grid, .footer-grid {
542
772
  display: grid;
@@ -568,6 +798,7 @@ export function renderIngestDossierAsHtml(dossier) {
568
798
  }
569
799
 
570
800
  .fact-card strong {
801
+ color: var(--text);
571
802
  font-size: 1rem;
572
803
  line-height: 1.45;
573
804
  overflow-wrap: anywhere;
@@ -608,11 +839,11 @@ export function renderIngestDossierAsHtml(dossier) {
608
839
  opacity: 0.75;
609
840
  }
610
841
 
611
- .tone-accent { background: var(--primary-soft); color: var(--primary); }
612
- .tone-good { background: var(--success-soft); color: var(--success); }
613
- .tone-warning { background: var(--warning-soft); color: var(--warning); }
614
- .tone-danger { background: var(--danger-soft); color: var(--danger); }
615
- .tone-neutral { background: var(--neutral-soft); color: var(--neutral); }
842
+ .tone-accent { background: var(--primary-soft); color: #22d3ee; border-color: rgba(6, 182, 212, 0.28); }
843
+ .tone-good { background: var(--success-soft); color: #34d399; border-color: rgba(16, 185, 129, 0.28); }
844
+ .tone-warning { background: var(--warning-soft); color: #fcd34d; border-color: rgba(245, 158, 11, 0.3); }
845
+ .tone-danger { background: var(--danger-soft); color: #f87171; border-color: rgba(239, 68, 68, 0.32); }
846
+ .tone-neutral { background: var(--neutral-soft); color: #cbd5e1; border-color: rgba(100, 116, 139, 0.28); }
616
847
 
617
848
  .action-bar {
618
849
  display: flex;
@@ -621,15 +852,15 @@ export function renderIngestDossierAsHtml(dossier) {
621
852
  }
622
853
 
623
854
  .action-bar button, .action-bar a {
624
- border: 1px solid var(--border);
625
- border-radius: 12px;
626
- background: var(--surface);
627
- color: var(--text);
628
- padding: 10px 13px;
855
+ border: 1px solid var(--border-strong);
856
+ border-radius: 8px;
857
+ background: var(--surface-soft);
858
+ color: #cbd5e1;
859
+ padding: 9px 12px;
629
860
  font: inherit;
630
861
  font-weight: 800;
631
862
  text-decoration: none;
632
- box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
863
+ box-shadow: none;
633
864
  cursor: pointer;
634
865
  overflow-wrap: anywhere;
635
866
  }
@@ -637,7 +868,68 @@ export function renderIngestDossierAsHtml(dossier) {
637
868
  .action-bar .primary-action {
638
869
  background: var(--primary);
639
870
  border-color: var(--primary);
640
- color: #ffffff;
871
+ color: #06111f;
872
+ }
873
+
874
+ .review-bar {
875
+ position: fixed;
876
+ left: 50%;
877
+ bottom: 0;
878
+ z-index: 45;
879
+ display: flex;
880
+ align-items: center;
881
+ justify-content: space-between;
882
+ gap: 18px;
883
+ width: min(1180px, calc(100vw - 36px));
884
+ margin: 10px auto 0;
885
+ padding: 13px 14px;
886
+ border: 1px solid #263244;
887
+ border-radius: 18px 18px 0 0;
888
+ background: rgba(11, 18, 32, 0.94);
889
+ color: #f8fafc;
890
+ box-shadow: 0 -18px 38px rgba(11, 18, 32, 0.2);
891
+ backdrop-filter: blur(14px);
892
+ transform: translate(-50%, 120%);
893
+ transition: transform 180ms ease;
894
+ }
895
+
896
+ .review-bar.visible {
897
+ transform: translate(-50%, 0);
898
+ }
899
+
900
+ .review-bar > div:first-child {
901
+ display: grid;
902
+ gap: 3px;
903
+ min-width: 220px;
904
+ }
905
+
906
+ .review-bar strong { font-size: 0.95rem; }
907
+ .review-bar span { color: #94a3b8; font-size: 0.84rem; line-height: 1.4; }
908
+
909
+ .review-actions {
910
+ display: flex;
911
+ flex-wrap: wrap;
912
+ justify-content: flex-end;
913
+ gap: 8px;
914
+ }
915
+
916
+ .review-actions a, .review-actions button {
917
+ border: 1px solid #334155;
918
+ border-radius: 10px;
919
+ background: #111827;
920
+ color: #dbeafe;
921
+ padding: 8px 10px;
922
+ font: inherit;
923
+ font-size: 0.78rem;
924
+ font-weight: 850;
925
+ text-decoration: none;
926
+ cursor: pointer;
927
+ }
928
+
929
+ .review-actions .primary-action {
930
+ background: #38bdf8;
931
+ border-color: #38bdf8;
932
+ color: #0b1220;
641
933
  }
642
934
 
643
935
  .data-section {
@@ -655,6 +947,7 @@ export function renderIngestDossierAsHtml(dossier) {
655
947
  .section-heading h2 {
656
948
  font-size: 1.35rem;
657
949
  letter-spacing: 0;
950
+ color: var(--text);
658
951
  }
659
952
 
660
953
  .section-heading p {
@@ -690,26 +983,49 @@ export function renderIngestDossierAsHtml(dossier) {
690
983
  }
691
984
 
692
985
  .profile-shell, .table-shell {
693
- overflow-x: auto;
986
+ overflow: auto;
694
987
  border: 1px solid var(--border);
695
988
  border-radius: var(--radius);
696
989
  background: var(--surface);
697
990
  min-width: 0;
698
991
  max-width: 100%;
992
+ max-height: 560px;
699
993
  }
700
994
 
701
995
  .borehole-profile {
996
+ display: block;
997
+ min-width: 680px;
998
+ width: 100%;
999
+ height: auto;
1000
+ }
1001
+
1002
+ .cross-section-shell {
1003
+ background:
1004
+ linear-gradient(180deg, rgba(6, 182, 212, 0.06), rgba(16, 185, 129, 0.04)),
1005
+ var(--surface);
1006
+ max-height: 360px;
1007
+ }
1008
+
1009
+ .ground-cross-section {
702
1010
  display: block;
703
1011
  min-width: 760px;
704
1012
  width: 100%;
705
1013
  height: auto;
706
1014
  }
707
1015
 
708
- .profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #101828; }
709
- .profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #101828; }
710
- .profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #475467; }
711
- .profile-axis { font: 11px Inter, Segoe UI, sans-serif; fill: #667085; }
712
- .profile-water { font: 800 11px Inter, Segoe UI, sans-serif; fill: #0b4f8a; }
1016
+ .verification-note {
1017
+ color: var(--muted);
1018
+ font-weight: 700;
1019
+ line-height: 1.55;
1020
+ padding-left: 14px;
1021
+ border-left: 3px solid var(--warning);
1022
+ }
1023
+
1024
+ .profile-title { font: 800 15px Inter, Segoe UI, sans-serif; fill: #f8fafc; }
1025
+ .profile-label { font: 800 13px Inter, Segoe UI, sans-serif; fill: #f8fafc; }
1026
+ .profile-small { font: 11px Inter, Segoe UI, sans-serif; fill: #cbd5e1; }
1027
+ .profile-axis { font: 11px Inter, Segoe UI, sans-serif; fill: #94a3b8; }
1028
+ .profile-water { font: 800 11px Inter, Segoe UI, sans-serif; fill: #67e8f9; }
713
1029
 
714
1030
  .profile-notes {
715
1031
  color: var(--muted);
@@ -724,19 +1040,20 @@ export function renderIngestDossierAsHtml(dossier) {
724
1040
 
725
1041
  th, td {
726
1042
  padding: 13px 14px;
727
- border-bottom: 1px solid rgba(11, 79, 138, 0.09);
1043
+ border-bottom: 1px solid rgba(100, 116, 139, 0.22);
728
1044
  text-align: left;
729
1045
  vertical-align: top;
730
1046
  font-size: 0.92rem;
731
1047
  line-height: 1.48;
732
1048
  overflow-wrap: anywhere;
1049
+ color: #cbd5e1;
733
1050
  }
734
1051
 
735
1052
  th {
736
1053
  position: sticky;
737
1054
  top: 0;
738
1055
  z-index: 1;
739
- background: var(--surface-soft);
1056
+ background: var(--surface-raised);
740
1057
  color: var(--muted);
741
1058
  font-size: 0.76rem;
742
1059
  text-transform: uppercase;
@@ -757,6 +1074,71 @@ export function renderIngestDossierAsHtml(dossier) {
757
1074
  line-height: 1;
758
1075
  }
759
1076
 
1077
+ .trust-controls {
1078
+ display: flex;
1079
+ align-items: end;
1080
+ justify-content: space-between;
1081
+ gap: 12px;
1082
+ flex-wrap: wrap;
1083
+ padding: 14px;
1084
+ border: 1px solid var(--border);
1085
+ border-radius: var(--radius);
1086
+ background: var(--surface);
1087
+ }
1088
+
1089
+ .trust-controls label {
1090
+ display: grid;
1091
+ gap: 7px;
1092
+ flex: 1 1 320px;
1093
+ color: var(--muted);
1094
+ font-size: 0.75rem;
1095
+ text-transform: uppercase;
1096
+ font-weight: 900;
1097
+ }
1098
+
1099
+ .trust-controls input {
1100
+ width: 100%;
1101
+ border: 1px solid var(--border-strong);
1102
+ border-radius: 12px;
1103
+ background: #0b1120;
1104
+ color: var(--text);
1105
+ padding: 11px 12px;
1106
+ font: inherit;
1107
+ font-size: 0.92rem;
1108
+ outline: none;
1109
+ text-transform: none;
1110
+ font-weight: 600;
1111
+ }
1112
+
1113
+ .trust-controls input:focus {
1114
+ border-color: var(--primary);
1115
+ box-shadow: 0 0 0 4px rgba(6, 182, 212, 0.12);
1116
+ }
1117
+
1118
+ .filter-row {
1119
+ display: flex;
1120
+ gap: 8px;
1121
+ flex-wrap: wrap;
1122
+ }
1123
+
1124
+ .filter-button {
1125
+ border: 1px solid var(--border-strong);
1126
+ border-radius: 999px;
1127
+ background: var(--surface-soft);
1128
+ color: var(--muted);
1129
+ padding: 9px 12px;
1130
+ font: inherit;
1131
+ font-size: 0.8rem;
1132
+ font-weight: 850;
1133
+ cursor: pointer;
1134
+ }
1135
+
1136
+ .filter-button.active {
1137
+ background: var(--primary);
1138
+ color: #06111f;
1139
+ border-color: var(--primary);
1140
+ }
1141
+
760
1142
  .evidence-grid {
761
1143
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
762
1144
  }
@@ -785,6 +1167,38 @@ export function renderIngestDossierAsHtml(dossier) {
785
1167
  line-height: 1.45;
786
1168
  }
787
1169
 
1170
+ .evidence-actions {
1171
+ display: flex;
1172
+ flex-wrap: wrap;
1173
+ gap: 8px;
1174
+ padding-top: 4px;
1175
+ }
1176
+
1177
+ .evidence-actions a, .evidence-actions button {
1178
+ border: 1px solid var(--border-strong);
1179
+ border-radius: 10px;
1180
+ background: #0b1120;
1181
+ color: #67e8f9;
1182
+ padding: 8px 9px;
1183
+ font: inherit;
1184
+ font-size: 0.78rem;
1185
+ font-weight: 850;
1186
+ text-decoration: none;
1187
+ cursor: pointer;
1188
+ }
1189
+
1190
+ .evidence-actions button[data-state="verified"] {
1191
+ color: var(--success);
1192
+ border-color: rgba(22, 135, 93, 0.28);
1193
+ background: var(--success-soft);
1194
+ }
1195
+
1196
+ .evidence-actions button[data-state="flagged"] {
1197
+ color: var(--warning);
1198
+ border-color: rgba(183, 121, 31, 0.28);
1199
+ background: var(--warning-soft);
1200
+ }
1201
+
788
1202
  .empty-state {
789
1203
  padding: 18px;
790
1204
  border: 1px dashed var(--border);
@@ -817,10 +1231,10 @@ export function renderIngestDossierAsHtml(dossier) {
817
1231
  .audit-drawer small { color: var(--muted); }
818
1232
 
819
1233
  .disclosure-hint {
820
- border: 1px solid var(--border);
1234
+ border: 1px solid var(--border-strong);
821
1235
  border-radius: 999px;
822
1236
  padding: 7px 11px;
823
- color: var(--primary);
1237
+ color: #67e8f9;
824
1238
  background: var(--primary-soft);
825
1239
  font-weight: 800;
826
1240
  white-space: nowrap;
@@ -879,9 +1293,9 @@ export function renderIngestDossierAsHtml(dossier) {
879
1293
  }
880
1294
 
881
1295
  .stage-chip {
882
- color: var(--primary);
1296
+ color: #67e8f9;
883
1297
  background: var(--primary-soft);
884
- border-color: #b9d4ec;
1298
+ border-color: rgba(6, 182, 212, 0.32);
885
1299
  }
886
1300
 
887
1301
  .footer-grid {
@@ -898,37 +1312,109 @@ export function renderIngestDossierAsHtml(dossier) {
898
1312
  line-height: 1.58;
899
1313
  }
900
1314
 
1315
+ tr[hidden] { display: none; }
1316
+
1317
+ .toast-region {
1318
+ position: fixed;
1319
+ top: 18px;
1320
+ right: 18px;
1321
+ z-index: 60;
1322
+ display: grid;
1323
+ gap: 8px;
1324
+ pointer-events: none;
1325
+ }
1326
+
1327
+ .toast {
1328
+ max-width: 320px;
1329
+ padding: 11px 14px;
1330
+ border: 1px solid #334155;
1331
+ border-radius: 12px;
1332
+ background: #0b1220;
1333
+ color: #f8fafc;
1334
+ box-shadow: 0 18px 38px rgba(11, 18, 32, 0.22);
1335
+ font-size: 0.86rem;
1336
+ font-weight: 700;
1337
+ }
1338
+
901
1339
  @media (max-width: 980px) {
902
- .layout { grid-template-columns: minmax(0, 1fr); padding: 18px; max-width: 100%; }
903
- .sidebar { position: static; min-height: auto; }
904
- .nav-list { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
1340
+ .layout { padding: 14px 16px 96px; max-width: 100%; }
1341
+ .sidebar { position: static; min-height: auto; align-items: flex-start; flex-direction: column; }
1342
+ .nav-list { justify-content: flex-start; }
905
1343
  .hero-main { grid-template-columns: 1fr; }
906
1344
  }
907
1345
 
908
1346
  @media (max-width: 620px) {
909
- .layout { display: block; width: 100%; max-width: 390px; margin: 0; padding: 12px; }
1347
+ body { padding-bottom: 0; }
1348
+ .layout {
1349
+ display: grid;
1350
+ width: min(100vw, 390px);
1351
+ max-width: 390px;
1352
+ margin: 0;
1353
+ padding: 10px;
1354
+ gap: 18px;
1355
+ overflow: hidden;
1356
+ }
910
1357
  .sidebar, .content, .hero, .data-section, .audit-drawer, .footer-grid {
911
1358
  width: 100%;
912
1359
  max-width: 100%;
913
1360
  }
1361
+ .sidebar { padding: 14px; min-height: auto; border-radius: 14px; }
1362
+ .brand { gap: 8px; padding-bottom: 0; margin-bottom: 0; }
1363
+ .brand span { display: none; }
1364
+ .nav-list {
1365
+ width: 100%;
1366
+ display: grid;
1367
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1368
+ gap: 5px;
1369
+ }
1370
+ .nav-list a {
1371
+ min-width: 0;
1372
+ padding: 7px 8px;
1373
+ font-size: 0.74rem;
1374
+ line-height: 1.25;
1375
+ }
914
1376
  .content { margin-top: 18px; }
915
- .hero { padding: 22px; }
1377
+ .hero { padding: 20px; width: 100%; max-width: 100%; }
1378
+ .hero-main { width: 100%; max-width: 100%; grid-template-columns: minmax(0, 1fr); }
1379
+ .hero-main > div, .hero-meta, .status-badge-row {
1380
+ width: 100%;
1381
+ max-width: min(320px, 100%);
1382
+ }
916
1383
  .executive-grid, .metric-grid, .card-grid, .evidence-grid, .footer-grid { grid-template-columns: 1fr; }
917
- .nav-list { grid-template-columns: 1fr; }
918
- h1 { font-size: 1.72rem; max-width: 100%; }
1384
+ .review-bar { position: static; transform: none; width: 100%; margin-top: 18px; border-radius: 18px; align-items: stretch; }
1385
+ .review-bar.visible { transform: none; }
1386
+ .review-actions { justify-content: flex-start; }
1387
+ h1 { font-size: 1.72rem; max-width: min(320px, 100%); line-height: 1.08; }
1388
+ .hero-subtitle { max-width: min(320px, 100%); font-size: 0.96rem; line-height: 1.35; overflow-wrap: anywhere; }
1389
+ .hero-summary { max-width: min(320px, 100%); font-size: 0.95rem; line-height: 1.55; }
1390
+ .status-badge-row { display: grid; grid-template-columns: minmax(0, 1fr); }
1391
+ .status-badge {
1392
+ width: 100%;
1393
+ max-width: 100%;
1394
+ min-width: 0;
1395
+ padding: 9px 10px;
1396
+ border-radius: 18px;
1397
+ }
1398
+ .status-badge span, .status-badge strong {
1399
+ min-width: 0;
1400
+ overflow-wrap: anywhere;
1401
+ word-break: break-word;
1402
+ }
1403
+ table { min-width: 760px; }
919
1404
  }
920
1405
  </style>
921
1406
  </head>
922
1407
  <body>
923
1408
  <div class="layout">
924
- <aside class="sidebar" aria-label="Dossier navigation">
1409
+ <aside class="sidebar" aria-label="Report navigation">
925
1410
  <div class="brand">
926
- <strong>Geotechnical Intelligence Dossier</strong>
1411
+ <strong>GeotechCLI Intelligence</strong>
927
1412
  <span>AI-assisted extraction, verification, and engineering interpretation from geotechnical reports.</span>
928
1413
  </div>
929
1414
  <nav class="nav-list">
930
1415
  <a href="#overview">Overview</a>
931
1416
  <a href="#ground-model">Ground Model</a>
1417
+ <a href="#ground-cross-section">Cross-Section</a>
932
1418
  <a href="#boreholes">Boreholes</a>
933
1419
  <a href="#parameters">Engineering Parameters</a>
934
1420
  <a href="#risks">Risks and Limitations</a>
@@ -941,10 +1427,11 @@ export function renderIngestDossierAsHtml(dossier) {
941
1427
  <header class="hero" id="overview">
942
1428
  <div class="hero-main">
943
1429
  <div>
944
- <span class="eyebrow">Geotechnical Intelligence Dossier</span>
945
- <h1>${escapeHtml(dossier.title)}</h1>
1430
+ <span class="eyebrow">Review Report</span>
1431
+ <h1>Geotechnical Intelligence Report</h1>
946
1432
  <p class="hero-subtitle">${escapeHtml(subtitle)}</p>
947
1433
  <p class="hero-summary">${escapeHtml(dossier.summary)}</p>
1434
+ ${renderStatusBadges(dossier)}
948
1435
  </div>
949
1436
  <div class="hero-meta">
950
1437
  <strong>${escapeHtml(dossier.sourceLabel)}</strong>
@@ -984,6 +1471,7 @@ export function renderIngestDossierAsHtml(dossier) {
984
1471
  </div>
985
1472
  </section>
986
1473
 
1474
+ ${renderGroundModelCrossSection(dossier.boreholeProfile)}
987
1475
  ${renderBoreholeProfile(dossier.boreholeProfile)}
988
1476
  ${renderTrustTable(dossier)}
989
1477
  ${mainTables.map((table) => renderTable(table)).join('')}
@@ -1017,6 +1505,67 @@ export function renderIngestDossierAsHtml(dossier) {
1017
1505
  </section>
1018
1506
  </main>
1019
1507
  </div>
1508
+ ${renderReviewBar(dossier)}
1509
+ <div class="toast-region" aria-live="polite" aria-atomic="true"></div>
1510
+ <script>
1511
+ (() => {
1512
+ const search = document.getElementById('trust-search');
1513
+ const rows = Array.from(document.querySelectorAll('[data-trust-row]'));
1514
+ const buttons = Array.from(document.querySelectorAll('[data-trust-filter]'));
1515
+ const toastRegion = document.querySelector('.toast-region');
1516
+ const reviewBar = document.querySelector('.review-bar');
1517
+ let activeFilter = 'all';
1518
+
1519
+ const showToast = (message) => {
1520
+ if (!toastRegion) return;
1521
+ const toast = document.createElement('div');
1522
+ toast.className = 'toast';
1523
+ toast.textContent = message;
1524
+ toastRegion.appendChild(toast);
1525
+ window.setTimeout(() => toast.remove(), 2600);
1526
+ };
1527
+
1528
+ const applyTrustFilter = () => {
1529
+ const query = String(search?.value ?? '').trim().toLowerCase();
1530
+ rows.forEach((row) => {
1531
+ const text = row.getAttribute('data-search') ?? '';
1532
+ const review = row.getAttribute('data-review') ?? '';
1533
+ const queryMatch = !query || text.includes(query);
1534
+ const filterMatch = activeFilter === 'all' || review === activeFilter;
1535
+ row.hidden = !(queryMatch && filterMatch);
1536
+ });
1537
+ };
1538
+
1539
+ const syncReviewBar = () => {
1540
+ if (!reviewBar) return;
1541
+ reviewBar.classList.toggle('visible', window.innerWidth > 620 && window.scrollY > 640);
1542
+ };
1543
+
1544
+ syncReviewBar();
1545
+ window.addEventListener('scroll', syncReviewBar, { passive: true });
1546
+ window.addEventListener('resize', syncReviewBar);
1547
+
1548
+ search?.addEventListener('input', applyTrustFilter);
1549
+ buttons.forEach((button) => {
1550
+ button.addEventListener('click', () => {
1551
+ activeFilter = button.getAttribute('data-trust-filter') ?? 'all';
1552
+ buttons.forEach((candidate) => candidate.classList.toggle('active', candidate === button));
1553
+ applyTrustFilter();
1554
+ });
1555
+ });
1556
+
1557
+ document.querySelectorAll('[data-review-action]').forEach((control) => {
1558
+ control.addEventListener('click', () => {
1559
+ const state = control.getAttribute('data-review-action') ?? '';
1560
+ control.setAttribute('data-state', state);
1561
+ control.textContent = state === 'verified' ? 'Verified' : 'Issue flagged';
1562
+ showToast(state === 'verified'
1563
+ ? 'Evidence item marked verified in this local report view.'
1564
+ : 'Evidence item flagged for engineering review in this local report view.');
1565
+ });
1566
+ });
1567
+ })();
1568
+ </script>
1020
1569
  </body>
1021
1570
  </html>`;
1022
1571
  }