feat: integrate cabin CAN and verified dynamic seat modes

This commit is contained in:
vee
2026-08-04 17:51:38 +08:00
parent 155fbb59ba
commit dc7c91800e
99 changed files with 51897 additions and 3618 deletions
+133 -32
View File
@@ -2641,7 +2641,7 @@ function updateSeatFeedbackModeLabel() {
}
function selectExecutionMode(mode) {
if (mode === "hardware" && !state.runtime?.hardware?.configured) return;
if (mode === "hardware" && !(state.runtime?.hardware?.configured && state.runtime?.vehicle_can?.configured)) return;
if (mode === "simulated_hardware" && !state.runtime?.simulated_hardware?.enabled) return;
state.executionMode = mode;
$$('[data-execution-mode]').forEach((button) => {
@@ -2663,7 +2663,7 @@ function selectExecutionMode(mode) {
? "真实台架安全门禁"
: simulatedHardware ? "模拟台架隔离边界" : "虚拟执行边界";
$("#safetyText").textContent = hardware
? "副驾座椅映射到统一实体执行器;绝对位置会实时检查板端标定,小幅相对移动可走受限定时/Hall 回读,异常会尝试 STOP_ALL。"
? "同一 CAN 盒由单进程持有:通道 0 控制副驾座椅并校验板端反馈,通道 1 以 ISO CAN FD 500 kbit/s / 2 Mbit/s 控制空调等车身设备并校验状态帧。"
: simulatedHardware
? "故障只注入执行报告;物理 CAN、ZLG 适配器和 IAR 固件路径保持关闭。"
: "四域 CAN 均为 dry-run,真实发送被强制关闭。";
@@ -2807,6 +2807,11 @@ function modeChipLabel(mode) {
: name;
}
function modeSecondsLabel(durationMs) {
const seconds = Number(durationMs) / 1000;
return Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1);
}
function populateModeChips() {
const group = $(".mode-chips");
if (!group) return;
@@ -2825,7 +2830,41 @@ function populateModeChips() {
}
button.dataset.modeProfileId = mode.id;
button.dataset.modeCommand = `进入${mode.name}`;
button.title = mode.description || mode.name;
const timeline = mode.timeline;
let cycleBadge = $(".mode-cycle-badge", button);
if (["infinite", "until_stopped_or_timeout"].includes(timeline?.repeat)) {
if (!cycleBadge) {
cycleBadge = document.createElement("small");
cycleBadge.className = "mode-cycle-badge";
button.append(cycleBadge);
}
const runtimeMinutes = Number(timeline.max_runtime_ms || 0) / 60000;
const runtimeLabel = Number.isInteger(runtimeMinutes)
? String(runtimeMinutes)
: runtimeMinutes.toFixed(1);
const cycleDurationMs = Number(timeline.cycle_duration_ms || 0);
const cycleLabel = cycleDurationMs > 0
? `${modeSecondsLabel(cycleDurationMs)}s循环`
: "目标到位循环";
cycleBadge.textContent = runtimeMinutes > 0
? `${cycleLabel} · 最长${runtimeLabel}min`
: cycleLabel;
button.dataset.modeBehavior = "cyclic";
} else if (cycleBadge) {
cycleBadge.remove();
}
const phaseSummary = Array.isArray(timeline?.phases)
? timeline.phases.map((phase) => {
if (Number(phase.duration_ms || 0) > 0) {
return `${phase.name} ${modeSecondsLabel(phase.duration_ms)}s`;
}
const timeout = Number(phase.timeout_ms || 0);
return timeout > 0
? `${phase.name} 目标到位(保护上限${modeSecondsLabel(timeout)}s)`
: `${phase.name} 目标到位`;
}).join(" → ")
: "";
button.title = [mode.description || mode.name, phaseSummary].filter(Boolean).join("\n");
});
refreshIcons();
}
@@ -2864,6 +2903,8 @@ async function loadRuntime() {
populateVehicleSelector();
populateModeChips();
const hardware = state.runtime.hardware || {};
const vehicleCan = state.runtime.vehicle_can || {};
const linkedHardwareReady = Boolean(hardware.configured && vehicleCan.configured);
const badge = $("#modelBadge");
const gptButton = $('[data-provider="openai"]');
if (state.runtime.openai_available) {
@@ -2882,7 +2923,7 @@ async function loadRuntime() {
}
$("#matrixCount").textContent = `${state.runtime.matrix.messages} / ${state.runtime.matrix.signals}`;
const hardwareButton = $("#hardwareMode");
hardwareButton.disabled = !hardware.configured;
hardwareButton.disabled = !linkedHardwareReady;
const simulation = state.runtime.simulated_hardware || {};
$("#simulationMode").disabled = !simulation.enabled;
$("#simulationMode").title = simulation.enabled
@@ -2897,23 +2938,23 @@ async function loadRuntime() {
const accessText = hardware.access_policy === "local_approval_idle_lease"
? ` · 本地审批 · 空闲 ${hardware.access_inactivity_timeout_seconds || 180} 秒自动停止`
: "";
$("#hardwareNote").textContent = hardware.configured
? `${hardware.backend} · ${hardware.bitrate / 1000} kbit/s · ${profileText} · ${flashText} · ${approvalText}${accessText}${concurrencyText}`
: `真实执行已锁定:${profileText};请完成服务端启用、适配器、固件镜像${hardware.approval_required ? "和令牌" : ""}配置`;
$("#hardwareAliasNote").textContent = "副驾座椅 → t2g_primary_seat(统一电机、加热和通风执行器)";
$("#hardwareNote").textContent = linkedHardwareReady
? `${hardware.backend} · CH${hardware.channel} 座椅 ${hardware.bitrate / 1000} kbit/s · CH${vehicleCan.channel} 车身 CAN FD ${vehicleCan.arbitration.bitrate / 1000}k/${vehicleCan.data.bitrate / 1000000}M · ${profileText} · ${flashText} · ${approvalText}${accessText}${concurrencyText}`
: `联动真实执行已锁定:${profileText};请完成座椅固件、双通道 ZLG 适配器、车身 CAN FD${hardware.approval_required ? "和令牌" : ""}配置`;
$("#hardwareAliasNote").textContent = "CH0 → 副驾座椅控制器 · CH1 → 空调/车窗/门锁/灯光/遮阳帘(ISO CAN FD)";
$("#autoFlashNote").textContent = hardware.auto_flash_enabled
? "未检测到匹配固件时,将在发送座椅控制帧前通过 IAR C-SPY 自动烧录并重新验证。"
: "仅接受周期反馈与四轴量程签名匹配的统一固件;需要自动烧录时请启用服务端开关。";
$("#hardwareBadge").className = `status-badge ${hardware.configured ? "" : "danger"}`.trim();
$("#hardwareBadge").innerHTML = hardware.configured
? '<i data-lucide="shield-check"></i>硬件已配置'
$("#hardwareBadge").className = `status-badge ${linkedHardwareReady ? "" : "danger"}`.trim();
$("#hardwareBadge").innerHTML = linkedHardwareReady
? '<i data-lucide="shield-check"></i>双通道已配置'
: '<i data-lucide="shield-off"></i>硬件锁定';
$("#hardwareCoverage").textContent = hardware.configured
? `可选 · ${approvalText}${accessText}${concurrencyText}`
$("#hardwareCoverage").textContent = linkedHardwareReady
? `CH0 座椅 + CH${vehicleCan.channel} 车身 · ${approvalText}${accessText}${concurrencyText}`
: "已阻断";
$("#hardwareCoverage").className = hardware.configured ? "available" : "blocked";
$("#hardwareCoverage").className = linkedHardwareReady ? "available" : "blocked";
const modeAvailable = state.executionMode === "hardware"
? hardware.configured
? linkedHardwareReady
: state.executionMode === "simulated_hardware"
? simulation.enabled
: true;
@@ -3136,6 +3177,20 @@ function mergeSeatActuatorState(previousState, patchState) {
mergedSeat.position = mergedPosition;
return;
}
if (feature === "dynamic_mode") {
const item = $(`.seat-card[data-seat="${seat}"] [data-dynamic-mode]`);
if (!item) return;
const running = value.state === "running";
item.classList.toggle("running", running);
const runtimeMinutes = Number(value.max_runtime_ms || 600000) / 60000;
const runtimeLabel = Number.isInteger(runtimeMinutes)
? String(runtimeMinutes)
: runtimeMinutes.toFixed(1);
$("b", item).textContent = running
? `${value.name} · 循环中 · 最长${runtimeLabel}分钟`
: "未运行";
return;
}
mergedSeat[feature] = value && typeof value === "object" && !Array.isArray(value)
? { ...(previousSeat[feature] || {}), ...value }
: value;
@@ -3317,6 +3372,10 @@ const CONFIG_DOMAIN_META = {
climate: { label: "空调", icon: "fan", className: "climate" },
ambient_light: { label: "氛围灯", icon: "lamp-ceiling", className: "ambient" },
roof: { label: "智能天幕", icon: "panels-top-left", className: "roof" },
door: { label: "车门锁", icon: "door-open", className: "generic" },
window: { label: "车窗", icon: "panels-top-left", className: "generic" },
outlet: { label: "电动风口", icon: "wind", className: "climate" },
interior_light: { label: "车内灯", icon: "lightbulb", className: "ambient" },
};
const CONFIG_CAPABILITY_LABELS = {
@@ -3328,6 +3387,8 @@ const CONFIG_CAPABILITY_LABELS = {
"seat.set_backrest_recline": "调节靠背角度",
"seat.set_legrest_height": "调节腿托角度",
"seat.set_legrest_extension": "调节腿托伸缩",
"seat.start_dynamic_mode": "启动动态座椅模式",
"seat.stop_dynamic_mode": "停止动态座椅模式",
"climate.set_temperature": "设置空调温度",
"climate.set_fan_level": "设置空调风量",
"climate.set_power": "设置空调电源",
@@ -3335,6 +3396,15 @@ const CONFIG_CAPABILITY_LABELS = {
"climate.set_cycle_mode": "设置内外循环",
"climate.set_outlet_mode": "设置出风模式",
"climate.set_front_defrost": "设置前风挡除霜",
"climate.set_control_mode": "设置空调控制模式",
"door.set_rear_lock": "设置后排门锁",
"window.set_motion": "控制车窗动作",
"window.set_position": "设置车窗开度",
"outlet.set_position_percent": "设置电动风口位置",
"outlet.set_calibrated_position": "设置电动风口标定位置",
"interior_light.set_indoor_power": "设置室内灯",
"interior_light.set_wall_power": "设置壁灯",
"interior_light.set_reading_power": "设置阅读灯",
"ambient_light.set_power": "设置氛围灯电源",
"ambient_light.set_illumination": "设置氛围灯颜色与亮度",
"ambient_light.set_color": "设置氛围灯颜色",
@@ -3342,6 +3412,7 @@ const CONFIG_CAPABILITY_LABELS = {
"ambient_light.set_mode": "设置氛围灯模式",
"roof.set_transmittance": "设置天幕透光率",
"roof.set_shade_position": "设置遮阳帘位置",
"roof.set_shade_motion": "控制遮阳帘动作",
"roof.set_automatic_dimming": "设置自动调光",
};
@@ -3361,6 +3432,7 @@ const CONFIG_PARAMETER_LABELS = {
cycle_mode: "循环模式",
outlet_mode: "出风模式",
mode: "工作模式",
mode_id: "固件模式编号",
};
function makeConfigElement(tagName, className, textValue) {
@@ -3427,8 +3499,13 @@ function renderConfigCards(result) {
const container = $("#configCards");
container.replaceChildren();
const entries = configEntries(result);
const operations = entries.flatMap(([domain, config]) => (
(config?.spec?.operations || []).map((operation) => ({ domain, config, operation }))
const operations = entries.flatMap(([configKey, config]) => (
(config?.spec?.operations || []).map((operation) => ({
configKey,
domain: config?.spec?.domain || configKey,
config,
operation,
}))
));
if (!operations.length) {
const empty = makeConfigElement("div", "config-empty");
@@ -3457,7 +3534,7 @@ function renderConfigCards(result) {
sceneSummary.append(sceneCopy, sceneStats);
container.append(sceneSummary);
operations.forEach(({ domain, config, operation }, index) => {
operations.forEach(({ configKey, domain, config, operation }, index) => {
const meta = CONFIG_DOMAIN_META[domain] || { label: domain, icon: "box", className: "generic" };
const card = makeConfigElement("article", `config-card config-${meta.className}`);
const header = makeConfigElement("header", "config-card-header");
@@ -3499,7 +3576,7 @@ function renderConfigCards(result) {
});
parametersBlock.append(parameterGrid);
const plan = planForDomain(result, domain);
const plan = planForDomain(result, configKey);
const allCommands = plan?.spec?.commands || [];
const matchedCommands = allCommands.filter((command) => !command.operation_ref || command.operation_ref === operation.id);
const command = matchedCommands[0] || null;
@@ -3528,10 +3605,16 @@ function renderConfigCards(result) {
hardwareBlock.append(signalList);
const footer = makeConfigElement("footer", "config-card-footer");
const executionScope = config.metadata?.labels?.execution_scope;
const executionScopeLabel = result.execution_mode === "hardware" && domain === "seat"
? "座椅 CAN · CH0"
: executionScope === "vehicle_can_channel_1"
? "车身 CAN · CH1"
: executionScope === "virtual_only" ? "仅虚拟执行" : "已通过配置校验";
footer.append(
makeConfigElement("span", "", `超时 ${operation.timeout_ms ?? "--"} ms`),
makeConfigElement("span", "", operation.failure_policy === "continue" ? "失败后继续" : "失败即中止"),
makeConfigElement("span", "", config.metadata?.labels?.execution_scope === "virtual_only" ? "仅虚拟执行" : "已通过配置校验"),
makeConfigElement("span", "", executionScopeLabel),
);
card.append(header, objectBlock, parametersBlock, hardwareBlock, footer);
@@ -3605,7 +3688,8 @@ function renderExecution(result) {
return;
}
const reportSucceeded = report.spec.status === "succeeded";
summary.className = `execution-summary ${reportSucceeded ? "succeeded" : "failed"}`;
const partiallySucceeded = result.status === "partially_succeeded";
summary.className = `execution-summary ${reportSucceeded ? "succeeded" : partiallySucceeded ? "partial" : "failed"}`;
const presetSave = Boolean(result.preset_config);
const modeSave = Boolean(result.mode_profile);
const simulatedHil = report.metadata?.labels?.seat_feedback === "simulated_hardware";
@@ -3614,6 +3698,10 @@ function renderExecution(result) {
const motionCount = report.spec.results.filter((item) => item.telemetry?.motion_axis).length;
const firmwareResult = report.spec.results.find((item) => item.telemetry?.firmware_verified);
const operationResults = report.spec.results.filter((item) => item.ref !== "seat-controller-firmware");
const succeededOperations = operationResults.filter((item) => item.status === "succeeded");
const failedOperations = operationResults.filter((item) => item.status === "failed");
const succeededDomains = [...new Set(succeededOperations.map((item) => item.domain))];
const failedDomains = [...new Set(failedOperations.map((item) => item.domain))];
let executionDetail = modeSave
? `长期模式 ${result.mode_profile.name} 已写入配置库,未生成 CAN 命令`
: presetSave
@@ -3623,13 +3711,16 @@ function renderExecution(result) {
: physical
? `${executedDomains.join(" / ")} 已下发真实 CAN,并以 IAR 固件实际反馈确认 ${operationResults.filter((item) => item.status === "succeeded").length} 个步骤`
: `${executedDomains.join(" / ")} 共 ${report.spec.results.length} 个操作已完成 CAN 编码与虚拟执行`;
if (partiallySucceeded) {
executionDetail = `部分执行完成:${succeededDomains.join(" / ")} 成功 ${succeededOperations.length} 项;${failedDomains.join(" / ")} 未完成 ${failedOperations.length} 项,失败设备未阻断其他设备`;
}
if (motionCount) executionDetail += `,含 ${motionCount} 个座椅位置电机反馈`;
if (firmwareResult?.telemetry?.programmed_this_request) executionDetail += ";本次已自动烧录并校验统一固件";
else if (firmwareResult) executionDetail += ";板上统一固件协议签名已校验";
if (result.status === "partially_succeeded") {
if (result.deferred_requests?.length) {
executionDetail += ";未接入域请求已保留为 deferred request";
}
if (!reportSucceeded) {
if (!reportSucceeded && !partiallySucceeded) {
const failure = report.spec.results.find((item) => item.status === "failed");
const failureTelemetry = failure?.telemetry || {};
executionDetail = simulatedHil
@@ -3655,13 +3746,18 @@ function renderExecution(result) {
: presetSave ? "姿态预设已保存"
: simulatedHil ? "模拟台架执行成功"
: physical ? "真实台架执行成功" : "虚拟执行成功"
: simulatedHil ? "模拟故障已触发" : physical ? "真实台架执行失败" : "虚拟执行失败";
summary.innerHTML = `<i data-lucide="${reportSucceeded ? ((presetSave || modeSave) ? "bookmark-check" : "circle-check-big") : "circle-x"}"></i><div><strong>${executionTitle}</strong><span>${executionDetail}</span></div>`;
: partiallySucceeded ? (physical ? "真实台架部分执行成功" : "部分执行成功")
: simulatedHil ? "模拟故障已触发" : physical ? "真实台架执行失败" : "虚拟执行失败";
const executionIcon = reportSucceeded
? ((presetSave || modeSave) ? "bookmark-check" : "circle-check-big")
: partiallySucceeded ? "circle-alert" : "circle-x";
summary.innerHTML = `<i data-lucide="${executionIcon}"></i><div><strong>${executionTitle}</strong><span>${executionDetail}</span></div>`;
$("#resultNavMeta").textContent = reportSucceeded
? `${report.spec.results.length} 项完成 · ${simulatedHil ? "SIM-HIL" : physical ? "HIL" : "DRY-RUN"}`
: "执行失败 · 查看回读";
$("#resultPanelState").textContent = reportSucceeded ? "SUCCEEDED" : "FAILED";
$("#resultPanelState").className = `workspace-panel-state ${reportSucceeded ? "succeeded" : "failed"}`;
: partiallySucceeded ? `${succeededOperations.length} 项成功 / ${failedOperations.length} 项失败 · 查看明细`
: "执行失败 · 查看回读";
$("#resultPanelState").textContent = reportSucceeded ? "SUCCEEDED" : partiallySucceeded ? "PARTIAL" : "FAILED";
$("#resultPanelState").className = `workspace-panel-state ${reportSucceeded ? "succeeded" : partiallySucceeded ? "partial" : "failed"}`;
report.spec.results.forEach((item) => {
const row = document.createElement("div");
row.className = "result-item";
@@ -3706,7 +3802,8 @@ function renderResult(result) {
} else if (result.function_configs?.seat) {
state.seatFeedbackSource = "simulated";
}
const executionSucceeded = ["succeeded", "partially_succeeded"].includes(result.status);
const partiallySucceeded = result.status === "partially_succeeded";
const executionSucceeded = result.status === "succeeded" || partiallySucceeded;
const domains = Object.keys(result.function_configs || {});
const domainLabels = {
seat: "座椅",
@@ -3714,7 +3811,9 @@ function renderResult(result) {
roof: "天幕",
ambient_light: "氛围灯",
};
$("#resultTitle").textContent = executionSucceeded
$("#resultTitle").textContent = partiallySucceeded
? `${domains.length} 域场景部分完成`
: executionSucceeded
? result.mode_profile
? "长期模式配置已保存"
: result.preset_config
@@ -3727,7 +3826,7 @@ function renderResult(result) {
$("#requestMeta").textContent = `${result.request_id} / context:${conversationLabel} / turn:${result.conversation_turn} / ${result.provider_used || result.provider_requested}`;
$("#requestMeta").textContent += ` / ${result.execution_report?.spec?.mode || result.execution_mode || "dry_run"}`;
setRunStatus(
executionSucceeded ? "succeeded" : "failed",
partiallySucceeded ? "partial" : executionSucceeded ? "succeeded" : "failed",
result.status.toUpperCase(),
);
renderPipeline(result.trace_events || []);
@@ -3818,7 +3917,9 @@ async function submitRequest(event) {
const completed = ["succeeded", "partially_succeeded"].includes(payload.status);
const simulated = payload.execution_report?.metadata?.labels?.seat_feedback === "simulated_hardware";
showToast(completed
? (simulated
? (payload.status === "partially_succeeded"
? "已执行可用设备;部分设备未完成,详见执行结果"
: simulated
? "模拟台架执行完成"
: payload.execution_report?.spec?.mode === "hil"
? "真实台架执行与反馈确认已完成"
+8 -2
View File
@@ -9,13 +9,13 @@
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/icons/app-icon-192.png" />
<title>智舱随行 · 一句话与手势控制</title>
<link rel="stylesheet" href="/styles.css?v=android-pad-2" />
<link rel="stylesheet" href="/styles.css?v=android-pad-3" />
<script src="/vendor/lucide/0.468.0/lucide.min.js" defer></script>
<script src="/vendor/three/0.147.0/three.min.js" defer></script>
<script src="/vendor/three/0.147.0/fflate.min.js" defer></script>
<script src="/vendor/three/0.147.0/FBXLoader.js" defer></script>
<script src="/network.js?v=android-pad-1" defer></script>
<script src="/app.js?v=seat-hil-9" defer></script>
<script src="/app.js?v=seat-hil-13" defer></script>
<script type="module" src="/gesture-control.js?v=android-pad-1"></script>
<script src="/pwa.js?v=android-pad-1" defer></script>
</head>
@@ -155,6 +155,9 @@
<div class="example-group">
<span class="field-label">示例请求</span>
<button type="button" class="example-command featured-example" data-example="副驾座椅通风3档,同时打开空调,温度设为24度,风量3档">
<i data-lucide="workflow"></i><span>副驾通风 + 空调 24℃ · 双通道联动</span><i data-lucide="chevron-right"></i>
</button>
<button type="button" class="example-command" data-example="空调打开,车内温度调到24度,风量3档">
<i data-lucide="fan"></i><span>空调 24℃ · 风量 3 档</span><i data-lucide="chevron-right"></i>
</button>
@@ -378,6 +381,9 @@
<div class="seat-grid">
<article class="seat-card" data-seat="passenger">
<div class="seat-identity"><i data-lucide="armchair"></i><div><strong>副驾</strong><span>front_right</span></div></div>
<div class="dynamic-mode-state" data-dynamic-mode>
<i data-lucide="waves"></i><span>动态模式</span><b>未运行</b>
</div>
<div class="thermal-states">
<span data-function="ventilation"><i data-lucide="wind"></i>通风 <b>--</b></span>
<span data-function="heating"><i data-lucide="flame"></i>加热 <b>--</b></span>
+3 -3
View File
@@ -1,12 +1,12 @@
const CACHE_VERSION = "cognitive-agent-pad-v9";
const CACHE_VERSION = "cognitive-agent-pad-v13";
const SHELL_CACHE = `${CACHE_VERSION}-shell`;
const RUNTIME_CACHE = `${CACHE_VERSION}-runtime`;
const APP_SHELL = [
"/",
"/index.html",
"/styles.css?v=android-pad-2",
"/styles.css?v=android-pad-3",
"/network.js?v=android-pad-1",
"/app.js?v=seat-hil-9",
"/app.js?v=seat-hil-13",
"/gesture-control.js?v=android-pad-1",
"/pwa.js?v=android-pad-1",
"/manifest.webmanifest",
+53
View File
@@ -1325,6 +1325,11 @@ textarea:focus {
background: var(--teal-soft);
}
.run-status.partial {
color: var(--amber);
background: var(--amber-soft);
}
.run-status.failed {
color: var(--red);
background: var(--red-soft);
@@ -2733,6 +2738,11 @@ pre {
border-left-color: var(--teal);
}
.execution-summary.partial {
background: var(--amber-soft);
border-left-color: var(--amber);
}
.execution-summary.failed {
background: var(--red-soft);
border-left-color: var(--red);
@@ -3892,6 +3902,11 @@ pre {
background: var(--teal-soft);
}
.workspace-panel-state.partial {
color: var(--amber);
background: var(--amber-soft);
}
.workspace-panel-state.failed {
color: var(--red);
background: var(--red-soft);
@@ -4357,6 +4372,44 @@ pre {
}
}
.mode-cycle-badge {
margin-left: 2px;
padding: 2px 6px;
border-radius: 999px;
background: rgba(89, 214, 255, 0.12);
color: #8be3ff;
font-size: 10px;
letter-spacing: 0.02em;
}
.dynamic-mode-state {
display: flex;
align-items: center;
gap: 8px;
margin: 10px 0;
padding: 9px 11px;
border: 1px solid rgba(126, 160, 194, 0.2);
border-radius: 10px;
color: #90a7bc;
background: rgba(13, 27, 41, 0.55);
}
.dynamic-mode-state b {
margin-left: auto;
color: #b9cad8;
font-size: 12px;
}
.dynamic-mode-state.running {
border-color: rgba(89, 214, 255, 0.48);
color: #8be3ff;
box-shadow: inset 0 0 20px rgba(89, 214, 255, 0.06);
}
.dynamic-mode-state.running b {
color: #e0f8ff;
}
.hardware-access-field {
padding: 12px;
border: 1px solid rgba(38, 102, 159, 0.24);