@jjlmoya/utils-forensic-science 1.1.0 → 1.2.0

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.
Files changed (32) hide show
  1. package/package.json +3 -2
  2. package/src/category/i18n/es.ts +9 -9
  3. package/src/category/index.ts +3 -1
  4. package/src/entries.ts +5 -1
  5. package/src/index.ts +1 -0
  6. package/src/tests/locale_completeness.test.ts +2 -2
  7. package/src/tests/tool_validation.test.ts +2 -2
  8. package/src/tool/bloodstain-pattern-origin-analyzer/bibliography.astro +9 -0
  9. package/src/tool/bloodstain-pattern-origin-analyzer/bibliography.ts +7 -0
  10. package/src/tool/bloodstain-pattern-origin-analyzer/bloodstain-pattern-origin-analyzer.css +438 -0
  11. package/src/tool/bloodstain-pattern-origin-analyzer/component.astro +558 -0
  12. package/src/tool/bloodstain-pattern-origin-analyzer/entry.ts +32 -0
  13. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/de.ts +119 -0
  14. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/en.ts +119 -0
  15. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/es.ts +119 -0
  16. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/fr.ts +119 -0
  17. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/id.ts +119 -0
  18. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/it.ts +119 -0
  19. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/ja.ts +119 -0
  20. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/ko.ts +119 -0
  21. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/nl.ts +119 -0
  22. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/pl.ts +119 -0
  23. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/pt.ts +119 -0
  24. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/ru.ts +119 -0
  25. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/sv.ts +119 -0
  26. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/tr.ts +119 -0
  27. package/src/tool/bloodstain-pattern-origin-analyzer/i18n/zh.ts +119 -0
  28. package/src/tool/bloodstain-pattern-origin-analyzer/index.ts +11 -0
  29. package/src/tool/bloodstain-pattern-origin-analyzer/logic.test.ts +23 -0
  30. package/src/tool/bloodstain-pattern-origin-analyzer/logic.ts +137 -0
  31. package/src/tool/bloodstain-pattern-origin-analyzer/seo.astro +10 -0
  32. package/src/tools.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jjlmoya/utils-forensic-science",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -43,7 +43,8 @@
43
43
  "@jjlmoya/prompagate": "^1.1.0",
44
44
  "@jjlmoya/utils-shared": "1.2.0",
45
45
  "astro": "^6.1.2",
46
- "astro-icon": "^1.1.0"
46
+ "astro-icon": "^1.1.0",
47
+ "three": "^0.185.0"
47
48
  },
48
49
  "peerDependencies": {
49
50
  "leaflet": "^1.9.4"
@@ -26,10 +26,10 @@ export const content: CategoryLocaleContent = {
26
26
  {
27
27
  type: 'list',
28
28
  items: [
29
- '<strong>Age assessment tools:</strong> dental, skeletal, and maturity indicators with explicit uncertainty.',
30
- '<strong>Evidence interpretation tools:</strong> structured calculators that show assumptions, confidence, and limitations.',
31
- '<strong>Case triage tools:</strong> fast workflows that help decide what data are missing before formal analysis.',
32
- '<strong>Reporting support:</strong> outputs that encourage clear language, ranges, caveats, and traceable reasoning.',
29
+ '<strong>Herramientas de evaluación de edad:</strong> indicadores dentales, esqueléticos y de madurez con incertidumbre explícita.',
30
+ '<strong>Herramientas de interpretación de evidencia:</strong> calculadoras estructuradas que muestran supuestos, confianza y limitaciones.',
31
+ '<strong>Herramientas de triaje de casos:</strong> flujos de trabajo rápidos que ayudan a decidir qué datos faltan antes del análisis formal.',
32
+ '<strong>Soporte para informes:</strong> resultados que fomentan un lenguaje claro, rangos, advertencias y razonamiento rastreable.',
33
33
  ],
34
34
  },
35
35
  {
@@ -43,12 +43,12 @@ export const content: CategoryLocaleContent = {
43
43
  },
44
44
  {
45
45
  type: 'table',
46
- headers: ['Good use', 'Poor use', 'Why it matters'],
46
+ headers: ['Buen uso', 'Mal uso', 'Por qué es importante'],
47
47
  rows: [
48
- ["Screen a case file before specialist review.", "Replace specialist review with a calculator result.", "Forensic conclusions must be defensible and methodologically valid."],
49
- ["Explain uncertainty to non-specialists.", "Report one exact answer without caveats.", "False precision can mislead legal or safeguarding decisions."],
50
- ["Compare how assumptions affect a result.", "Hide assumptions from the report.", "Transparent assumptions make the result easier to audit."],
51
- ["Identify missing evidence.", "Ignore poor data quality.", "Weak inputs can make even a correct formula unreliable."],
48
+ ["Examinar un expediente antes de la revisión especializada.", "Reemplazar la revisión de un especialista con el resultado de una calculadora.", "Las conclusiones forenses deben ser defendibles y metodológicamente válidas."],
49
+ ["Explicar la incertidumbre a no especialistas.", "Informar una sola respuesta exacta sin advertencias.", "La falsa precisión puede inducir a error en decisiones legales o de protección."],
50
+ ["Comparar cómo influyen los supuestos en un resultado.", "Ocultar supuestos del informe.", "Los supuestos transparentes hacen que el resultado sea más fácil de auditar."],
51
+ ["Identificar la evidencia faltante.", "Ignorar la mala calidad de los datos.", "Los datos de entrada débiles pueden hacer que incluso una fórmula correcta no sea confiable."],
52
52
  ],
53
53
  },
54
54
  ],
@@ -10,6 +10,7 @@ import { forensicTlcInkSimulator } from '../tool/forensic-tlc-ink-simulator/inde
10
10
  import { forensicMicrocrystalDrugSimulator } from '../tool/forensic-microcrystal-drug-simulator/index';
11
11
  import { forensicGlassBeckeLineSimulator } from '../tool/forensic-glass-becke-line-simulator/index';
12
12
  import { forensicFiberComparisonMicroscope } from '../tool/forensic-fiber-comparison-microscope/index';
13
+ import { bloodstainPatternOriginAnalyzer } from '../tool/bloodstain-pattern-origin-analyzer/index';
13
14
 
14
15
  export const forensicCategory: ScienceCategoryEntry = {
15
16
  icon: 'mdi:fingerprint',
@@ -24,7 +25,8 @@ export const forensicCategory: ScienceCategoryEntry = {
24
25
  forensicTlcInkSimulator,
25
26
  forensicMicrocrystalDrugSimulator,
26
27
  forensicGlassBeckeLineSimulator,
27
- forensicFiberComparisonMicroscope
28
+ forensicFiberComparisonMicroscope,
29
+ bloodstainPatternOriginAnalyzer
28
30
  ],
29
31
  i18n: {
30
32
  de: () => import('./i18n/de').then((m) => m.content),
package/src/entries.ts CHANGED
@@ -20,6 +20,8 @@ export { forensicGlassBeckeLineSimulator } from './tool/forensic-glass-becke-lin
20
20
  export type { GlassBeckeLineSimulatorUI, GlassBeckeLineSimulatorLocaleContent } from './tool/forensic-glass-becke-line-simulator/entry';
21
21
  export { forensicFiberComparisonMicroscope } from './tool/forensic-fiber-comparison-microscope/entry';
22
22
  export type { FiberComparisonMicroscopeUI, FiberComparisonMicroscopeLocaleContent } from './tool/forensic-fiber-comparison-microscope/entry';
23
+ export { bloodstainPatternOriginAnalyzer } from './tool/bloodstain-pattern-origin-analyzer/entry';
24
+ export type { BloodstainPatternUI, BloodstainPatternLocaleContent } from './tool/bloodstain-pattern-origin-analyzer/entry';
23
25
 
24
26
  import { forensicAgeEstimator } from './tool/forensic-age-estimator/entry';
25
27
  import { widmarkAlcoholSimulator } from './tool/widmark-alcohol-simulator/entry';
@@ -32,6 +34,7 @@ import { forensicTlcInkSimulator } from './tool/forensic-tlc-ink-simulator/entry
32
34
  import { forensicMicrocrystalDrugSimulator } from './tool/forensic-microcrystal-drug-simulator/entry';
33
35
  import { forensicGlassBeckeLineSimulator } from './tool/forensic-glass-becke-line-simulator/entry';
34
36
  import { forensicFiberComparisonMicroscope } from './tool/forensic-fiber-comparison-microscope/entry';
37
+ import { bloodstainPatternOriginAnalyzer } from './tool/bloodstain-pattern-origin-analyzer/entry';
35
38
 
36
39
  export const ALL_ENTRIES = [
37
40
  forensicAgeEstimator,
@@ -44,5 +47,6 @@ export const ALL_ENTRIES = [
44
47
  forensicTlcInkSimulator,
45
48
  forensicMicrocrystalDrugSimulator,
46
49
  forensicGlassBeckeLineSimulator,
47
- forensicFiberComparisonMicroscope
50
+ forensicFiberComparisonMicroscope,
51
+ bloodstainPatternOriginAnalyzer
48
52
  ];
package/src/index.ts CHANGED
@@ -11,6 +11,7 @@ export { FORENSIC_TLC_INK_SIMULATOR_TOOL } from './tool/forensic-tlc-ink-simulat
11
11
  export { FORENSIC_MICROCRYSTAL_DRUG_SIMULATOR_TOOL } from './tool/forensic-microcrystal-drug-simulator/index';
12
12
  export { FORENSIC_GLASS_BECKE_LINE_SIMULATOR_TOOL } from './tool/forensic-glass-becke-line-simulator/index';
13
13
  export { FORENSIC_FIBER_COMPARISON_MICROSCOPE_TOOL } from './tool/forensic-fiber-comparison-microscope/index';
14
+ export { BLOODSTAIN_PATTERN_ORIGIN_ANALYZER_TOOL } from './tool/bloodstain-pattern-origin-analyzer/index';
14
15
 
15
16
  export type {
16
17
  KnownLocale,
@@ -18,7 +18,7 @@ describe('Locale Completeness Validation', () => {
18
18
  });
19
19
  });
20
20
 
21
- it('all 11 tools registered', () => {
22
- expect(ALL_TOOLS.length).toBe(11);
21
+ it('all 12 tools registered', () => {
22
+ expect(ALL_TOOLS.length).toBe(12);
23
23
  });
24
24
  });
@@ -4,8 +4,8 @@ import { scienceCategory } from '../data';
4
4
 
5
5
  describe('Tool Validation Suite', () => {
6
6
  describe('Library Registration', () => {
7
- it('should have 11 tools in ALL_TOOLS', () => {
8
- expect(ALL_TOOLS.length).toBe(11);
7
+ it('should have 12 tools in ALL_TOOLS', () => {
8
+ expect(ALL_TOOLS.length).toBe(12);
9
9
  });
10
10
 
11
11
  it('scienceCategory should be defined', () => {
@@ -0,0 +1,9 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bloodstainPatternOriginAnalyzer } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+ interface Props { locale?: KnownLocale; }
6
+ const { locale = 'en' } = Astro.props;
7
+ const content = await bloodstainPatternOriginAnalyzer.i18n[locale]?.();
8
+ ---
9
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,7 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ { name: 'SWGSTAIN - Scientific Working Group on Bloodstain Pattern Analysis documents', url: 'https://www.nist.gov/organization-scientific-area-committees-forensic-science/bloodstain-pattern-analysis-subcommittee' },
5
+ { name: 'NIST - Bloodstain Pattern Analysis Subcommittee resources', url: 'https://www.nist.gov/organization-scientific-area-committees-forensic-science/bloodstain-pattern-analysis' },
6
+ { name: 'Bevel, T. & Gardner, R. M. - Bloodstain Pattern Analysis with an Introduction to Crime Scene Reconstruction', url: 'https://www.routledge.com/Bloodstain-Pattern-Analysis-with-an-Introduction-to-Crime-Scene-Reconstruction/Bevel-Gardner/p/book/9780367778109' },
7
+ ];
@@ -0,0 +1,438 @@
1
+ .bpa-shell {
2
+ --bpa-ink: #32151c;
3
+ --bpa-muted: #78525a;
4
+ --bpa-line: rgba(94, 28, 43, 0.16);
5
+ --bpa-panel: #fff6f1;
6
+ --bpa-panel-strong: #fff;
7
+ --bpa-red: #b3203f;
8
+ --bpa-red-soft: rgba(179, 32, 63, 0.1);
9
+ --bpa-gold: #d89d2b;
10
+ --bpa-teal: #18b79d;
11
+
12
+ display: grid;
13
+ gap: 1rem;
14
+ width: 100%;
15
+ color: var(--bpa-ink);
16
+ }
17
+
18
+ .theme-dark .bpa-shell {
19
+ --bpa-ink: #f4f7fb;
20
+ --bpa-muted: #a8b1bf;
21
+ --bpa-line: rgba(226, 232, 240, 0.14);
22
+ --bpa-panel: #111418;
23
+ --bpa-panel-strong: #171b21;
24
+ --bpa-red-soft: rgba(255, 74, 112, 0.16);
25
+ }
26
+
27
+ .theme-dark .bpa-card {
28
+ background:
29
+ linear-gradient(135deg, rgba(255,255,255,.035), transparent 34%),
30
+ var(--bpa-panel);
31
+ box-shadow: 0 24px 70px rgba(0, 0, 0, 0.34);
32
+ }
33
+
34
+ .bpa-card {
35
+ display: grid;
36
+ gap: 0.85rem;
37
+ width: 100%;
38
+ padding: clamp(0.75rem, 1.8vw, 1rem);
39
+ border: 1px solid var(--bpa-line);
40
+ border-radius: 8px;
41
+ background:
42
+ linear-gradient(135deg, rgba(255,255,255,.64), transparent 32%),
43
+ var(--bpa-panel);
44
+ box-shadow: 0 24px 70px rgba(65, 16, 28, 0.12);
45
+ }
46
+
47
+ .bpa-stage {
48
+ display: grid;
49
+ grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr);
50
+ gap: 0.85rem;
51
+ }
52
+
53
+ .bpa-surface-panel,
54
+ .bpa-view-panel,
55
+ .bpa-table-wrap {
56
+ min-width: 0;
57
+ overflow: hidden;
58
+ border: 1px solid var(--bpa-line);
59
+ border-radius: 8px;
60
+ background: color-mix(in srgb, var(--bpa-panel-strong) 70%, transparent);
61
+ }
62
+
63
+ .bpa-toolbar {
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: space-between;
67
+ gap: 0.75rem;
68
+ padding: 0.15rem 0.1rem;
69
+ }
70
+
71
+ .bpa-segment,
72
+ .bpa-actions {
73
+ display: flex;
74
+ gap: 0.45rem;
75
+ }
76
+
77
+ .bpa-active-strip {
78
+ display: inline-flex;
79
+ align-items: center;
80
+ min-height: 2.25rem;
81
+ margin-inline: auto;
82
+ padding: 0 0.85rem;
83
+ border: 1px solid var(--bpa-line);
84
+ border-radius: 999px;
85
+ background: color-mix(in srgb, var(--bpa-panel-strong) 82%, transparent);
86
+ color: var(--bpa-muted);
87
+ font-size: 0.88rem;
88
+ }
89
+
90
+ .bpa-active-strip span {
91
+ width: 0.72rem;
92
+ height: 0.72rem;
93
+ margin-right: 0.45rem;
94
+ border-radius: 999px;
95
+ box-shadow: 0 0 0 4px var(--bpa-red-soft);
96
+ }
97
+
98
+ .bpa-active-strip strong {
99
+ color: var(--bpa-ink);
100
+ }
101
+
102
+ .bpa-shell button {
103
+ display: inline-flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ gap: 0.35rem;
107
+ min-height: 2.25rem;
108
+ border: 1px solid var(--bpa-line);
109
+ border-radius: 7px;
110
+ background: var(--bpa-panel-strong);
111
+ color: var(--bpa-ink);
112
+ font-size: inherit;
113
+ font-weight: 750;
114
+ cursor: pointer;
115
+ transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
116
+ }
117
+
118
+ .bpa-shell button svg {
119
+ width: 1rem;
120
+ height: 1rem;
121
+ }
122
+
123
+ .bpa-shell button:hover,
124
+ .bpa-segment button.is-active {
125
+ border-color: rgba(179, 32, 63, 0.48);
126
+ background: var(--bpa-red);
127
+ color: white;
128
+ }
129
+
130
+ .bpa-shell button:hover {
131
+ transform: translateY(-1px);
132
+ }
133
+
134
+ .bpa-actions button,
135
+ .bpa-segment button {
136
+ padding: 0 0.8rem;
137
+ }
138
+
139
+ .bpa-surface-wrap {
140
+ padding: 0.65rem;
141
+ }
142
+
143
+ .bpa-surface-wrap canvas {
144
+ display: block;
145
+ width: 100%;
146
+ aspect-ratio: 1.55;
147
+ border-radius: 8px;
148
+ border: 1px solid var(--bpa-line);
149
+ cursor: crosshair;
150
+ touch-action: none;
151
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.45);
152
+ }
153
+
154
+ .bpa-surface-wrap canvas.can-grab {
155
+ cursor: grab;
156
+ }
157
+
158
+ .bpa-surface-wrap canvas.is-dragging {
159
+ cursor: grabbing;
160
+ }
161
+
162
+ .bpa-view-panel {
163
+ display: grid;
164
+ grid-template-rows: minmax(240px, 1fr) auto;
165
+ overflow: hidden;
166
+ }
167
+
168
+ .bpa-view-panel p {
169
+ margin: 0;
170
+ padding: 0.65rem 0.8rem;
171
+ border-top: 1px solid var(--bpa-line);
172
+ background: color-mix(in srgb, var(--bpa-panel-strong) 74%, transparent);
173
+ color: var(--bpa-muted);
174
+ font-size: 0.92rem;
175
+ }
176
+
177
+ .bpa-three {
178
+ min-height: 260px;
179
+ background:
180
+ radial-gradient(circle at 50% 35%, rgba(245, 198, 94, 0.12), transparent 34%),
181
+ radial-gradient(circle at 72% 25%, rgba(24, 183, 157, 0.08), transparent 30%),
182
+ linear-gradient(145deg, #171b21, #06080b);
183
+ }
184
+
185
+ .bpa-three canvas {
186
+ display: block;
187
+ width: 100%;
188
+ height: 100%;
189
+ }
190
+
191
+ .bpa-dashboard {
192
+ display: grid;
193
+ grid-template-columns: repeat(4, minmax(0, 1fr));
194
+ gap: 0;
195
+ overflow: hidden;
196
+ border: 1px solid var(--bpa-line);
197
+ border-radius: 8px;
198
+ background:
199
+ linear-gradient(90deg, var(--bpa-red-soft), transparent 42%),
200
+ color-mix(in srgb, var(--bpa-panel-strong) 66%, transparent);
201
+ }
202
+
203
+ .bpa-focus-panel {
204
+ display: grid;
205
+ grid-template-columns: minmax(110px, 0.7fr) repeat(3, minmax(150px, 1fr));
206
+ gap: 0.85rem;
207
+ align-items: center;
208
+ padding: 0.72rem 0.82rem;
209
+ border: 1px solid var(--bpa-line);
210
+ border-radius: 8px;
211
+ background:
212
+ linear-gradient(90deg, color-mix(in srgb, var(--bpa-red) 9%, transparent), transparent 55%),
213
+ color-mix(in srgb, var(--bpa-panel-strong) 76%, transparent);
214
+ }
215
+
216
+ .bpa-focus-title {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ gap: 0.55rem;
220
+ min-width: 0;
221
+ font-size: 0.95rem;
222
+ }
223
+
224
+ .bpa-focus-title span {
225
+ width: 0.8rem;
226
+ height: 0.8rem;
227
+ flex: 0 0 auto;
228
+ border-radius: 999px;
229
+ box-shadow: 0 0 0 5px var(--bpa-red-soft);
230
+ }
231
+
232
+ .bpa-focus-title strong {
233
+ overflow: hidden;
234
+ text-overflow: ellipsis;
235
+ white-space: nowrap;
236
+ }
237
+
238
+ .bpa-focus-panel label {
239
+ display: grid;
240
+ grid-template-columns: auto minmax(72px, 1fr) minmax(54px, auto);
241
+ gap: 0.55rem;
242
+ align-items: center;
243
+ min-width: 0;
244
+ color: var(--bpa-muted);
245
+ font-size: 0.78rem;
246
+ font-weight: 850;
247
+ text-transform: uppercase;
248
+ }
249
+
250
+ .bpa-focus-panel input[type="range"] {
251
+ width: 100%;
252
+ accent-color: var(--bpa-red);
253
+ }
254
+
255
+ .bpa-focus-panel output {
256
+ color: var(--bpa-ink);
257
+ font-size: 0.86rem;
258
+ font-weight: 850;
259
+ text-align: right;
260
+ text-transform: none;
261
+ }
262
+
263
+ .bpa-readout {
264
+ display: grid;
265
+ gap: 0.35rem;
266
+ padding: 0.85rem;
267
+ border-right: 1px solid var(--bpa-line);
268
+ position: relative;
269
+ }
270
+
271
+ .bpa-readout::before {
272
+ content: "";
273
+ position: absolute;
274
+ inset: 0 auto 0 0;
275
+ width: 3px;
276
+ background: transparent;
277
+ }
278
+
279
+ .bpa-readout:first-child::before {
280
+ background: var(--bpa-teal);
281
+ }
282
+
283
+ .bpa-readout:nth-child(2)::before {
284
+ background: var(--bpa-gold);
285
+ }
286
+
287
+ .bpa-readout:nth-child(3)::before {
288
+ background: var(--bpa-red);
289
+ }
290
+
291
+ .bpa-readout:nth-child(4)::before {
292
+ background: #6b7cff;
293
+ }
294
+
295
+ .bpa-readout:last-child {
296
+ border-right: 0;
297
+ }
298
+
299
+ .bpa-readout span {
300
+ color: var(--bpa-muted);
301
+ font-size: 0.82rem;
302
+ font-weight: 800;
303
+ text-transform: uppercase;
304
+ }
305
+
306
+ .bpa-readout strong {
307
+ font-size: clamp(1rem, 2vw, 1.35rem);
308
+ line-height: 1.15;
309
+ }
310
+
311
+ #bpa-confidence[data-confidence="high"] {
312
+ color: var(--bpa-teal);
313
+ }
314
+
315
+ #bpa-confidence[data-confidence="medium"] {
316
+ color: var(--bpa-gold);
317
+ }
318
+
319
+ #bpa-confidence[data-confidence="low"] {
320
+ color: var(--bpa-red);
321
+ }
322
+
323
+ .bpa-table-wrap {
324
+ overflow-x: auto;
325
+ }
326
+
327
+ .bpa-table {
328
+ width: 100%;
329
+ border-collapse: collapse;
330
+ min-width: 720px;
331
+ }
332
+
333
+ .bpa-table caption {
334
+ padding: 0.75rem 0.85rem;
335
+ text-align: left;
336
+ font-weight: 850;
337
+ }
338
+
339
+ .bpa-table th,
340
+ .bpa-table td {
341
+ padding: 0.48rem;
342
+ border-top: 1px solid var(--bpa-line);
343
+ }
344
+
345
+ .bpa-table th {
346
+ color: var(--bpa-muted);
347
+ font-size: 0.82rem;
348
+ text-align: left;
349
+ }
350
+
351
+ .bpa-table input {
352
+ width: 100%;
353
+ min-height: 2.25rem;
354
+ border: 1px solid var(--bpa-line);
355
+ border-radius: 7px;
356
+ background: var(--bpa-panel-strong);
357
+ color: var(--bpa-ink);
358
+ font-size: inherit;
359
+ font-weight: 700;
360
+ padding: 0 0.55rem;
361
+ transition: border-color 160ms ease, box-shadow 160ms ease;
362
+ }
363
+
364
+ .bpa-table input:focus {
365
+ border-color: rgba(179, 32, 63, 0.55);
366
+ box-shadow: 0 0 0 3px var(--bpa-red-soft);
367
+ outline: none;
368
+ }
369
+
370
+ .bpa-table tr.is-selected {
371
+ background: var(--bpa-red-soft);
372
+ }
373
+
374
+ .bpa-table tr.is-selected input {
375
+ border-color: rgba(179, 32, 63, 0.32);
376
+ }
377
+
378
+ .bpa-table td:last-child {
379
+ width: 3.2rem;
380
+ text-align: right;
381
+ }
382
+
383
+ .bpa-table td:last-child .bpa-icon-button {
384
+ display: inline-grid;
385
+ place-items: center;
386
+ width: 2.35rem;
387
+ padding: 0;
388
+ }
389
+
390
+ .bpa-icon-button svg {
391
+ width: 1.1rem;
392
+ height: 1.1rem;
393
+ pointer-events: none;
394
+ }
395
+
396
+ .bpa-disclaimer {
397
+ margin: 0;
398
+ padding: 0.85rem 1rem;
399
+ border-left: 4px solid var(--bpa-gold);
400
+ border-radius: 8px;
401
+ background:
402
+ linear-gradient(90deg, color-mix(in srgb, var(--bpa-gold) 18%, transparent), transparent),
403
+ color-mix(in srgb, var(--bpa-panel-strong) 72%, transparent);
404
+ color: var(--bpa-muted);
405
+ font-weight: 700;
406
+ }
407
+
408
+ @media (max-width: 860px) {
409
+ .bpa-stage,
410
+ .bpa-dashboard,
411
+ .bpa-focus-panel {
412
+ grid-template-columns: 1fr;
413
+ }
414
+
415
+ .bpa-toolbar {
416
+ align-items: stretch;
417
+ flex-direction: column;
418
+ }
419
+
420
+ .bpa-active-strip {
421
+ width: 100%;
422
+ justify-content: center;
423
+ }
424
+
425
+ .bpa-readout {
426
+ border-right: 0;
427
+ border-bottom: 1px solid var(--bpa-line);
428
+ }
429
+
430
+ .bpa-readout:last-child {
431
+ border-bottom: 0;
432
+ }
433
+
434
+ .bpa-segment button,
435
+ .bpa-actions button {
436
+ flex: 1;
437
+ }
438
+ }