feat: add guided demo view

This commit is contained in:
root
2026-07-04 01:21:59 +02:00
parent f3e2d16059
commit 2d50cdb454
10 changed files with 685 additions and 54 deletions

View File

@@ -30,6 +30,27 @@ https://www.arhipovdan.ru/
http://127.0.0.1:3100/
```
### Guided Demo
Рекомендуемый путь для жюри — использовать **Guided Demo** view.
В Header переключите режим на `Guided Demo`.
Это чистое демо-представление для защиты (1 экран без скролла).
Отличия Guided Demo от Engineering Dashboard:
- Guided Demo показывает только ключевую информацию для защиты (Proof Card, Narration, Snapshot), скрывая длинные логи и графики.
- Engineering Mode дает полный доступ ко всем метрикам, логам, PID-графику и дереву классификации.
Рекомендуемый путь:
- Start Guided Demo
- Next
- Oversized
- Round
- Low confidence
- Jam
- Emergency stop
В Guided Demo закрываются критерии: classification correctness, routing, timing, fault handling.
## Presentation Mode
В Header есть переключатель:

View File

@@ -1,56 +1,28 @@
# Demo Script
Open https://arhipovdan.ru/ and switch Header to `Presentation Mode`.
Open https://arhipovdan.ru/ and switch Header to `Guided Demo`.
## 3-Minute Defense
## 30-Second Pitch
### 0:00-0:25 — System Overview
"We built an engineering simulation of the OZON sorting cell. It models the full loop: detection -> classification -> command -> routing. We cover happy paths to B/C/D, fault handling, and timing. The Guided Demo view shows the live proof."
Demo step: `System overview`.
## 3-Minute Guided Demo
Say: the system models the full loop: detection -> classification -> actuator command -> physical route.
1. Open **Guided Demo**.
2. Click **Start Guided Demo**.
3. Point to the **Current Proof Card** and the **Scene**.
- "Watch how detection leads to classification and routing."
4. Click **Next** to show `Oversized item to C`.
- "Dimensions fail first, priority C."
5. Click **Next** to show `Round object to D`.
- "Dimensions pass, but roundness triggers D."
6. Click **Next** through `Boundary`, `Low confidence`, `Jam`, `Emergency stop`.
- On faults: click "Apply fault scenario" to confirm.
- "The line stops safely on emergency."
Watch: engineering SVG scene, sensors, stop-gate, pushers and B/C/D routes.
## 5-Minute Guided Demo
### 0:25-0:55 — Normal Item To B
Demo step: `Normal item to B`.
Apply scenario, use `Run suggested action` or `Step state`.
Say: dimensions pass, roundness is below threshold, category B opens the gate and sends the item straight.
### 0:55-1:25 — Oversized To C
Demo step: `Oversized item to C`.
Say: dimensions fail first, so C has priority even before shape routing.
Watch: decision tree FAIL, `ROUTE_TO_C`, lower roll-cage.
### 1:25-1:55 — Round Object To D
Demo step: `Round object to D`.
Say: dimensions pass, but roundness >= 0.8, so the item goes to D for repack/shape issue.
### 1:55-2:25 — Fault And Safety
Demo step: `Jam / fault handling` or `Emergency stop`.
With Safe Demo ON, confirm the fault scenario intentionally.
Say: abnormal states stop conveyor motion and require Reset.
### 2:25-3:00 — Criteria Coverage
Demo step: `Performance and synchronization`, then focus criteria panel.
Say: this checklist maps every OZON criterion to scenario, component and doc evidence.
## 5-Minute Defense
Use all 10 demo steps:
Use all 10 demo steps in Guided Demo View:
1. System overview.
2. Normal item to B.
@@ -65,11 +37,20 @@ Use all 10 demo steps:
For each step:
1. Read `What to watch`.
2. Apply recommended scenario.
3. Use `Step state` or `Run suggested action`.
4. Point to the highlighted focus area.
5. Close with the presenter phrase.
1. Read the **Demo Narration Card**.
2. Use **Next** or the **Primary Action Button**.
3. Point to the highlighted values in the **Proof Card**.
4. Read the **Presenter Phrase** from the screen.
## What to say on each step
Use the `presenterPhrase` text shown in the **Demo Narration Card**. It is specifically written to be short and clear for the jury.
## What to do if demo gets stuck
- Click **Reset Demo**.
- If still stuck, switch back to **Engineering Mode** and use manual scenario selection.
- Proceed manually.
## Fallback If Domain Does Not Open

View File

@@ -59,3 +59,19 @@ The stop-gate fixes the item. B opens the gate, C extends pusher C, D extends pu
## 15. What Happens On Jam?
The system enters FAULT, conveyor target speed becomes 0, actual speed decays toward 0, and Reset is required.
## 16. Why is the interface divided into Guided and Engineering?
Guided Demo is focused solely on the defense presentation, showing only the Proof Card, Scene, and Narrative, ensuring clarity in a 3-minute pitch. Engineering Mode provides full access to logs, PID, and internal state.
## 17. How does Guided Demo prove engineering realism?
Even in Guided Demo, the scene and proof card are driven by the real underlying state machine, classifier, and metric engine—not a pre-rendered video.
## 18. Where to see the full event log and PID?
In the Engineering Dashboard.
## 19. Why aren't all panels shown in Guided Demo?
To avoid cognitive overload during the pitch. The jury needs to understand the decision and see the route clearly without distraction from raw JSON logs or PID graphs.

View File

@@ -11,6 +11,13 @@ Before final submission:
- [ ] Presentation Mode opens.
- [ ] Demo steps checked.
- [ ] Safe Demo confirmation checked for jam/emergency.
- [ ] Guided Demo verified on 1920x1080 (no scroll).
- [ ] Guided Demo verified on 1440x900.
- [ ] Start Guided Demo button works.
- [ ] Next/Previous buttons work.
- [ ] Reset Demo button works.
- [ ] Fault confirmation works in Guided Demo.
- [ ] Engineering Mode is not broken.
- [ ] OZON Criteria Coverage Panel checked.
- [ ] Engineering Mode still works.
- [ ] Scenarios checked: normal_flow, oversized_item, round_object, boundary_dimensions, close_items, low_confidence, jam, emergency_stop.

View File

@@ -5,6 +5,8 @@ import { SCENARIOS } from './data/scenarios';
import { createSimulation, setRunning, stepSimulation, stepSimulationToNextState } from './domain/simulation';
import type { DemoStep, PresentationMode, ScenarioId, SimulationState } from './domain/types';
import GuidedDemoView from './components/GuidedDemoView';
const TICK_MS = 250;
function isFaultDemoStep(step: DemoStep): boolean {
@@ -13,7 +15,7 @@ function isFaultDemoStep(step: DemoStep): boolean {
export default function App() {
const [activeScenarioId, setActiveScenarioId] = useState<ScenarioId>('normal_flow');
const [presentationMode, setPresentationMode] = useState<PresentationMode>('engineering');
const [presentationMode, setPresentationMode] = useState<PresentationMode>('guided');
const [safeDemoEnabled, setSafeDemoEnabled] = useState(true);
const [demoStepIndex, setDemoStepIndex] = useState(0);
const [pendingFaultStepId, setPendingFaultStepId] = useState<string | undefined>();
@@ -87,7 +89,30 @@ export default function App() {
setActiveScenarioId(DEMO_STEPS[0].scenarioId);
};
return (
return presentationMode === 'guided' ? (
<GuidedDemoView
simulation={simulation}
scenarios={SCENARIOS}
activeScenarioId={activeScenarioId}
presentationMode={presentationMode}
safeDemoEnabled={safeDemoEnabled}
demoSteps={DEMO_STEPS}
demoStepIndex={demoStepIndex}
currentDemoStep={currentDemoStep}
pendingFaultStepId={pendingFaultStepId}
onStart={handleStart}
onPause={handlePause}
onReset={handleReset}
onStep={handleStep}
onScenarioChange={handleScenarioChange}
onPresentationModeChange={setPresentationMode}
onSafeDemoChange={setSafeDemoEnabled}
onDemoStepChange={handleDemoStepChange}
onApplyDemoStepScenario={handleApplyDemoStepScenario}
onRunPreferredAction={handleRunPreferredAction}
onRestartDemo={handleRestartDemo}
/>
) : (
<Dashboard
simulation={simulation}
scenarios={SCENARIOS}

View File

@@ -0,0 +1,98 @@
import { DIMENSION_LIMITS } from '../domain/classifier';
import type { SimulationState } from '../domain/types';
export default function CurrentProofCard({ simulation }: { simulation: SimulationState }) {
const current = simulation.currentItem;
const result = current?.classification;
const item = current?.item;
const dimensions = item?.dimensionsMm;
const lowConfidence = Boolean(item && item.confidence < 0.65);
const stopped = simulation.machineState === 'FAULT' || simulation.machineState === 'EMERGENCY_STOP';
if (!current) {
return (
<section className="proof-card proof-card-empty">
<div className="proof-card-inner">
<p className="eyebrow">Awaiting cycle</p>
<h2>Ready to demonstrate sorting cycle</h2>
<p>The demo will show detection, classification, control command and routing to B/C/D.</p>
<div className="evidence-chain empty-chain">
<span>Detection</span>
<span className="arrow">→</span>
<span>Classification</span>
<span className="arrow">→</span>
<span>Command</span>
<span className="arrow">→</span>
<span>Actuator</span>
<span className="arrow">→</span>
<span>Route</span>
<span className="arrow">→</span>
<span>Log</span>
</div>
<p className="start-prompt">Click <strong>Start Guided Demo</strong> or Next to begin.</p>
</div>
</section>
);
}
return (
<section className={`proof-card ${stopped ? 'proof-card-stopped' : ''}`}>
<div className="panel-heading">
<p className="eyebrow">Current Item Proof</p>
<h2>{item?.name} (ID: {item?.id})</h2>
</div>
<div className="proof-grid">
<div className="proof-item">
<span className="proof-label">Dimensions</span>
<strong className={result?.dimensionsPass === false ? 'fail-text' : ''}>
{dimensions?.width} x {dimensions?.depth} x {dimensions?.height} mm
</strong>
</div>
<div className="proof-item">
<span className="proof-label">Roundness</span>
<strong className={result?.roundnessPass === false && result?.dimensionsPass ? 'fail-text' : ''}>
{item?.roundness.toFixed(2)}
</strong>
</div>
<div className="proof-item">
<span className="proof-label">Confidence</span>
<strong className={lowConfidence ? 'warn-text' : ''}>
{(item?.confidence ?? 0 * 100).toFixed(0)}%
</strong>
</div>
<div className="proof-item">
<span className="proof-label">Category</span>
<strong className={`category-${result?.category}`}>{result?.category}</strong>
</div>
<div className="proof-item">
<span className="proof-label">Command</span>
<strong>{simulation.machineState.startsWith('ROUTE_TO_') ? simulation.machineState : 'WAITING'}</strong>
</div>
<div className="proof-item">
<span className="proof-label">Target zone</span>
<strong>{result?.category ? `Zone ${result.category}` : '-'}</strong>
</div>
</div>
<div className="proof-reason">
<strong>Decision reason:</strong> {result?.reason}
{lowConfidence && <p className="warning-note">Low CV confidence — rule-based fallback used</p>}
{simulation.machineState === 'FAULT' && <p className="error-note">FAULT: Conveyor stopped, reset required</p>}
{simulation.machineState === 'EMERGENCY_STOP' && <p className="error-note">EMERGENCY_STOP: All actuators stopped</p>}
{simulation.metrics.queueLength > 1 && <p className="warning-note">Spacing warning: sequential processing enforced</p>}
</div>
<div className="evidence-chain active-chain">
<span className={simulation.machineState === 'DETECTING' ? 'active-step' : 'done-step'}>Detection</span>
<span className="arrow">→</span>
<span className={simulation.machineState === 'CLASSIFYING' ? 'active-step' : (result ? 'done-step' : '')}>Classification</span>
<span className="arrow">→</span>
<span className={simulation.machineState.startsWith('ROUTE_TO_') ? 'active-step' : (result ? 'done-step' : '')}>Command</span>
<span className="arrow">→</span>
<span className={simulation.actuators.pusherC !== 'idle' || simulation.actuators.pusherD !== 'idle' ? 'active-step' : (result ? 'done-step' : '')}>Actuator</span>
<span className="arrow">→</span>
<span className={simulation.machineState.startsWith('ROUTE_TO_') ? 'active-step' : ''}>Route</span>
<span className="arrow">→</span>
<span className={simulation.machineState === 'RETURN_HOME' ? 'active-step' : ''}>Log</span>
</div>
</section>
);
}

View File

@@ -0,0 +1,182 @@
import { useEffect } from 'react';
import SorterScene from './SorterScene';
import CurrentProofCard from './CurrentProofCard';
import { OZON_CRITERIA } from '../data/criteria';
import type { DemoStep, Scenario, ScenarioId, SimulationState, PresentationMode } from '../domain/types';
interface GuidedDemoViewProps {
simulation: SimulationState;
scenarios: Scenario[];
activeScenarioId: ScenarioId;
presentationMode: PresentationMode;
safeDemoEnabled: boolean;
demoSteps: DemoStep[];
demoStepIndex: number;
currentDemoStep: DemoStep;
pendingFaultStepId?: string;
onStart: () => void;
onPause: () => void;
onReset: () => void;
onStep: () => void;
onScenarioChange: (scenarioId: ScenarioId) => void;
onPresentationModeChange: (mode: any) => void;
onSafeDemoChange: (enabled: boolean) => void;
onDemoStepChange: (index: number) => void;
onApplyDemoStepScenario: (step: DemoStep) => void;
onRunPreferredAction: (step: DemoStep) => void;
onRestartDemo: () => void;
}
export default function GuidedDemoView({
simulation,
scenarios,
activeScenarioId,
demoSteps,
demoStepIndex,
currentDemoStep,
pendingFaultStepId,
safeDemoEnabled,
onStart,
onPause,
onReset,
onStep,
onPresentationModeChange,
onDemoStepChange,
onApplyDemoStepScenario,
onRunPreferredAction,
onRestartDemo,
}: GuidedDemoViewProps) {
const isFaultStep = currentDemoStep.scenarioId === 'jam' || currentDemoStep.scenarioId === 'emergency_stop';
const pendingFaultConfirmation = pendingFaultStepId === currentDemoStep.id;
const handleStartDemo = () => {
if (!simulation.currentItem && demoStepIndex === 0 && !simulation.running) {
onRunPreferredAction(currentDemoStep);
} else if (demoStepIndex === 0 && simulation.running) {
onRestartDemo();
} else {
onRunPreferredAction(currentDemoStep);
}
};
const handleNext = () => {
onDemoStepChange(demoStepIndex + 1);
};
const handlePrevious = () => {
onDemoStepChange(demoStepIndex - 1);
};
const relatedCriteriaList = OZON_CRITERIA.filter(c => currentDemoStep.relatedCriteria.includes(c.id));
return (
<div className="guided-demo-layout">
{/* 1. Top Demo Header */}
<header className="guided-header">
<div className="guided-header-left">
<p className="eyebrow">Guided Sorting Demo</p>
<h1>Step {demoStepIndex + 1} / {demoSteps.length}: {currentDemoStep.title}</h1>
</div>
<div className="guided-header-center">
<div className={`status-pill status-${simulation.systemStatus.toLowerCase()}`}>
<span />
{simulation.systemStatus}
</div>
<div className="scenario-label">Scenario: {simulation.scenario.name}</div>
</div>
<div className="guided-header-right">
<button className="primary-action" onClick={handleStartDemo}>
{simulation.currentItem ? 'Restart Guided Demo' : 'Start Guided Demo'}
</button>
<div className="nav-actions">
<button onClick={handlePrevious} disabled={demoStepIndex === 0}>Previous</button>
<button onClick={handleNext} disabled={demoStepIndex === demoSteps.length - 1}>Next</button>
</div>
<button onClick={onReset}>Reset</button>
<button onClick={() => onPresentationModeChange('engineering')}>Back to Engineering</button>
</div>
</header>
{/* Main Grid */}
<main className="guided-grid">
{/* 2. Large Scene Area */}
<section className="guided-left">
<div className="panel panel-scene guided-scene-panel">
<SorterScene simulation={simulation} />
<div className="guided-metrics-strip">
<div className="guided-metric">
<span>Cycle Time</span>
<strong>{simulation.metrics.avgCycleTimeMs} ms</strong>
</div>
<div className="guided-metric">
<span>CV Latency</span>
<strong>{simulation.metrics.cvLatencyMs} ms</strong>
</div>
<div className="guided-metric">
<span>Actuator Latency</span>
<strong>{simulation.metrics.actuatorLatencyMs} ms</strong>
</div>
<div className="guided-metric">
<span>Throughput</span>
<strong>{simulation.metrics.throughputItemsPerMin} items/min</strong>
</div>
</div>
</div>
</section>
{/* Right Column */}
<aside className="guided-right">
{/* 3. Current Proof Card */}
<CurrentProofCard simulation={simulation} />
{/* 4. Demo Narration Card */}
<section className="panel guided-narration">
<div className="panel-heading">
<p className="eyebrow">Demo Narration</p>
<h2>{currentDemoStep.title}</h2>
</div>
<p className="explanation">{currentDemoStep.explanation}</p>
<div className="narration-details">
<div>
<strong>Watch:</strong> {currentDemoStep.whatToWatch}
</div>
<div>
<strong>Jury Value:</strong> {currentDemoStep.juryValue}
</div>
</div>
<blockquote className="presenter-phrase">
"{currentDemoStep.presenterPhrase}"
</blockquote>
{isFaultStep && safeDemoEnabled && (
<button
className={`fault-confirm-btn ${pendingFaultConfirmation ? 'pending' : ''}`}
onClick={() => onApplyDemoStepScenario(currentDemoStep)}
>
{pendingFaultConfirmation ? 'Click again to confirm fault scenario' : 'Apply fault scenario'}
</button>
)}
</section>
{/* 5. Criteria Snapshot */}
<section className="panel guided-criteria">
<div className="panel-heading">
<p className="eyebrow">Criteria Snapshot</p>
<h2>This step covers</h2>
</div>
<div className="criteria-list-mini">
{relatedCriteriaList.map(c => (
<div key={c.id} className="criterion-mini">
<div className="criterion-mini-header">
<strong>{c.title}</strong>
<span className={`criterion-status status-${c.status.replace(/ /g, '-')}`}>{c.status}</span>
</div>
<p>{c.evidence}</p>
</div>
))}
</div>
</section>
</aside>
</main>
</div>
);
}

View File

@@ -34,7 +34,8 @@ export default function Header({
<div className="header-status">
<div className="mode-toggle" role="group" aria-label="Dashboard mode">
<button className={presentationMode === 'engineering' ? 'active' : ''} onClick={() => onPresentationModeChange('engineering')}>Engineering Mode</button>
<button className={presentationMode === 'presentation' ? 'active' : ''} onClick={() => onPresentationModeChange('presentation')}>Presentation Mode</button>
<button className={presentationMode === 'presentation' ? 'active' : ''} onClick={() => onPresentationModeChange('presentation')}>Presentation Dashboard</button>
<button className={presentationMode === 'guided' ? 'active' : ''} onClick={() => onPresentationModeChange('guided')}>Guided Demo</button>
</div>
{presentationMode === 'presentation' ? (
<button className={safeDemoEnabled ? 'safe-demo-toggle active' : 'safe-demo-toggle'} onClick={() => onSafeDemoChange(!safeDemoEnabled)}>

View File

@@ -188,7 +188,7 @@ export interface SimulationState {
}
export type PresentationMode = 'engineering' | 'presentation';
export type PresentationMode = 'engineering' | 'presentation' | 'guided';
export type DemoPreferredAction = 'start' | 'step' | 'reset' | 'pause';
export type DemoFocusArea = 'scene' | 'classification' | 'timeline' | 'pid' | 'eventLog' | 'criteria' | 'safety';
export type CriteriaStatus = 'covered' | 'partially covered' | 'demo step available';

View File

@@ -1123,3 +1123,303 @@ button:disabled {
grid-template-columns: 1fr;
}
}
/* Guided Demo View */
.guided-demo-layout {
display: flex;
flex-direction: column;
height: 100vh;
padding: 16px;
overflow: hidden;
background: var(--bg);
}
.guided-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 20px;
background: rgba(7, 14, 23, 0.95);
border: 1px solid var(--line);
border-radius: 16px;
margin-bottom: 16px;
flex-shrink: 0;
}
.guided-header h1 {
font-size: 24px;
}
.guided-header-center {
display: flex;
gap: 12px;
}
.guided-header-right {
display: flex;
gap: 10px;
align-items: center;
}
.primary-action {
background: var(--cyan);
color: #000;
border-color: var(--cyan);
font-size: 14px;
padding: 0 16px;
}
.primary-action:hover {
background: #7dd3fc;
color: #000;
}
.nav-actions {
display: flex;
gap: 6px;
}
.guided-grid {
display: grid;
grid-template-columns: 1fr 420px;
gap: 16px;
flex-grow: 1;
min-height: 0;
}
.guided-left {
display: flex;
flex-direction: column;
min-height: 0;
}
.guided-scene-panel {
flex-grow: 1;
display: flex;
flex-direction: column;
padding: 16px;
min-height: 0;
}
.guided-scene-panel .scene-wrap {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.guided-scene-panel svg {
flex-grow: 1;
height: auto;
max-height: 100%;
}
.guided-metrics-strip {
display: flex;
justify-content: space-around;
gap: 12px;
margin-top: 16px;
padding: 12px;
background: rgba(0,0,0,0.2);
border-radius: 12px;
border: 1px solid var(--line);
flex-shrink: 0;
}
.guided-metric {
text-align: center;
}
.guided-metric span {
display: block;
font-size: 12px;
color: var(--muted);
margin-bottom: 4px;
}
.guided-metric strong {
font-size: 18px;
color: var(--text);
}
.guided-right {
display: flex;
flex-direction: column;
gap: 16px;
overflow-y: auto;
padding-right: 4px;
}
/* Proof Card */
.proof-card {
padding: 16px;
background: #081422;
border: 1px solid var(--cyan);
border-radius: 16px;
box-shadow: 0 0 20px rgba(56, 189, 248, 0.1);
flex-shrink: 0;
}
.proof-card-stopped {
border-color: var(--red);
box-shadow: 0 0 20px rgba(251, 61, 78, 0.15);
}
.proof-card-empty {
border-color: #243e57;
box-shadow: none;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
}
.proof-card-empty h2 {
font-size: 20px;
margin: 12px 0;
}
.proof-card-empty p {
color: var(--muted);
margin-bottom: 16px;
}
.proof-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
}
.proof-item {
background: #0d1b2a;
padding: 10px;
border-radius: 10px;
border: 1px solid #1a2f44;
}
.proof-label {
display: block;
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 4px;
}
.proof-item strong {
font-size: 16px;
}
.fail-text { color: var(--red); }
.warn-text { color: var(--orange); }
.proof-reason {
background: #0d1b2a;
padding: 12px;
border-radius: 10px;
border: 1px solid #1a2f44;
font-size: 13px;
margin-bottom: 16px;
}
.proof-reason .warning-note { margin-top: 8px; font-weight: bold; }
.proof-reason .error-note { margin-top: 8px; color: var(--red); font-weight: bold; }
.evidence-chain {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
font-weight: bold;
background: #050a10;
padding: 10px 14px;
border-radius: 8px;
border: 1px solid #1e3a54;
}
.evidence-chain span { color: #475569; }
.evidence-chain .arrow { color: #334155; }
.evidence-chain .done-step { color: var(--green); }
.evidence-chain .active-step { color: var(--cyan); text-shadow: 0 0 8px rgba(56, 189, 248, 0.5); }
/* Narration */
.guided-narration {
padding: 16px;
flex-shrink: 0;
}
.guided-narration .explanation {
font-size: 14px;
line-height: 1.5;
margin-bottom: 16px;
}
.narration-details {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 16px;
font-size: 13px;
background: #0d1b2a;
padding: 12px;
border-radius: 10px;
border: 1px solid #1a2f44;
}
.narration-details strong { color: var(--cyan); }
.presenter-phrase {
margin: 0;
padding: 14px;
background: rgba(56, 189, 248, 0.1);
border-left: 4px solid var(--cyan);
border-radius: 0 10px 10px 0;
font-size: 15px;
font-weight: 600;
font-style: italic;
line-height: 1.5;
}
.fault-confirm-btn {
margin-top: 12px;
width: 100%;
padding: 12px;
background: rgba(245, 158, 11, 0.1);
border: 1px solid var(--orange);
color: var(--orange);
}
.fault-confirm-btn.pending {
background: var(--orange);
color: #000;
}
/* Criteria Snapshot */
.guided-criteria {
padding: 16px;
flex-shrink: 0;
}
.criteria-list-mini {
display: flex;
flex-direction: column;
gap: 10px;
}
.criterion-mini {
background: #0d1b2a;
border: 1px solid #1a2f44;
padding: 12px;
border-radius: 10px;
}
.criterion-mini-header {
display: flex;
justify-content: space-between;
margin-bottom: 6px;
}
.criterion-mini-header strong { font-size: 13px; }
.criterion-mini p { font-size: 12px; color: var(--muted); margin: 0; line-height: 1.4; }