feat(n7web): enhance control workflows and status feedback
This commit is contained in:
@@ -0,0 +1,584 @@
|
||||
<script setup>
|
||||
defineOptions({ name: "N7ConsoleApp" });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="v2-boot-screen" id="v2BootScreen" role="status" aria-live="polite">
|
||||
<span class="v2-boot-mark" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
<strong>511工程-智能座舱</strong>
|
||||
<small>正在加载车机控制与实时状态</small>
|
||||
</div>
|
||||
<div class="workspace">
|
||||
<aside class="command-panel">
|
||||
<div class="panel-heading">
|
||||
<h2>说一句,就调好了</h2>
|
||||
</div>
|
||||
|
||||
<div class="mode-rail" aria-label="功能模式">
|
||||
<div class="mode-rail-bar">
|
||||
<div class="mode-rail-identity">
|
||||
<span class="mode-rail-mark" aria-hidden="true"><i data-lucide="sparkles"></i></span>
|
||||
<span><strong class="mode-rail-title">功能模式</strong><small>预设与我的模式</small></span>
|
||||
</div>
|
||||
<div class="mode-rail-content" id="modeRailContent" role="region" aria-label="功能模式列表">
|
||||
<div class="mode-chips" tabindex="0" aria-label="快捷功能模式,可横向滚动" aria-live="polite">
|
||||
<button type="button" data-mode-profile-id="zero_gravity" data-mode-origin="preset" data-mode-command="进入零重力模式"><i data-lucide="orbit"></i><span>零重力</span></button>
|
||||
<button type="button" data-mode-profile-id="office" data-mode-origin="preset" data-mode-command="进入办公模式"><i data-lucide="briefcase"></i><span>办公</span></button>
|
||||
<button type="button" data-mode-profile-id="nap" data-mode-origin="preset" data-mode-command="进入小憩模式"><i data-lucide="moon-star"></i><span>小憩</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mode-rail-actions">
|
||||
<button class="mode-gallery-toggle" id="modeGalleryToggle" type="button" aria-expanded="false" aria-controls="modeGallery"><i data-lucide="settings-2" data-icon="inline-start"></i><span>管理</span><strong id="modeGalleryCount">0</strong></button>
|
||||
<button class="mode-create-toggle" id="saveModeToggle" type="button"><i data-lucide="sparkles" data-icon="inline-start"></i><span>AI 新建</span></button>
|
||||
<button class="mode-rail-toggle" id="modeRailToggle" type="button" aria-expanded="true" aria-controls="modeRailContent" aria-label="收起功能模式" title="收起功能模式">
|
||||
<span class="visually-hidden" id="modeRailSelection">静态模式</span><i data-lucide="chevron-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="requestForm" autocomplete="off">
|
||||
<section class="assistant-transcript" id="assistantTranscript" role="log" aria-live="polite" aria-label="AI助手对话">
|
||||
<div class="assistant-messages" id="assistantMessages">
|
||||
<article class="assistant-message assistant-message-assistant">
|
||||
<span class="assistant-avatar" aria-hidden="true"><i data-lucide="waypoints"></i></span>
|
||||
<div class="assistant-message-body">
|
||||
<div class="assistant-message-content">你好,我是 AI助手。你可以向我提问,也可以直接交代需要完成的工作。</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<div class="field-label-row">
|
||||
<label for="utterance">向 AI助手提问或交代工作</label>
|
||||
<button class="context-reset" id="newConversation" type="button" title="开始新会话" aria-label="开始新会话">
|
||||
<i data-lucide="message-square-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="command-composer">
|
||||
<textarea id="utterance" name="utterance" maxlength="4000" autocomplete="off" placeholder="向 AI助手提问或交代工作"></textarea>
|
||||
<button class="speech-dictation" id="speechDictationButton" type="button" aria-label="开始语音输入" aria-pressed="false" title="开始语音输入">
|
||||
<i class="speech-mic-icon" data-lucide="mic" aria-hidden="true"></i>
|
||||
<span class="speech-waveform" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>
|
||||
<span class="speech-visually-hidden">开始语音输入</span>
|
||||
</button>
|
||||
<span class="speech-status" id="speechDictationStatus" role="status" aria-live="polite"></span>
|
||||
<span class="composer-hint">Enter 发送 · Shift + Enter 换行</span>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<span class="field-label">意图解析器</span>
|
||||
<div class="segmented" role="group" aria-label="意图解析器">
|
||||
<button type="button" class="segment active" data-provider="openai">
|
||||
<i data-lucide="sparkles"></i>GPT
|
||||
</button>
|
||||
<button type="button" class="segment" data-provider="local">
|
||||
<i data-lucide="braces"></i>本地规则
|
||||
</button>
|
||||
</div>
|
||||
<p class="provider-note" id="providerNote">正在读取运行时配置</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group" id="reasoningField">
|
||||
<span class="field-label">模型推理强度</span>
|
||||
<div class="segmented reasoning-segmented" role="group" aria-label="模型推理强度">
|
||||
<button type="button" class="segment reasoning-option" data-reasoning-effort="low">
|
||||
<i data-lucide="zap"></i><span><strong>极速</strong><small>简单指令,优先响应速度</small></span><i class="reasoning-check" data-lucide="check"></i>
|
||||
</button>
|
||||
<button type="button" class="segment reasoning-option active" data-reasoning-effort="medium">
|
||||
<i data-lucide="scale"></i><span><strong>均衡</strong><small>日常控制,兼顾速度与理解</small></span><i class="reasoning-check" data-lucide="check"></i>
|
||||
</button>
|
||||
<button type="button" class="segment reasoning-option" data-reasoning-effort="high">
|
||||
<i data-lucide="brain"></i><span><strong>深度</strong><small>复杂联动、相对动作与约束</small></span><i class="reasoning-check" data-lucide="check"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="provider-note" id="reasoningNote">均衡模式兼顾自然语言解析精度与响应速度</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group">
|
||||
<span class="field-label">执行目标</span>
|
||||
<div class="segmented execution-mode-options" role="group" aria-label="执行目标">
|
||||
<button type="button" class="mode-option" data-execution-mode="dry_run">
|
||||
<i data-lucide="flask-conical"></i>模拟下发
|
||||
</button>
|
||||
<button type="button" class="mode-option active" id="hardwareMode" data-execution-mode="hardware">
|
||||
<i data-lucide="radio-tower"></i>真实下发
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group" id="vehicleTransportField">
|
||||
<span class="field-label">车辆连接方式</span>
|
||||
<div class="segmented vehicle-transport-options" role="group" aria-label="车辆连接方式">
|
||||
<button type="button" class="mode-option active" data-control-transport="wired_can">
|
||||
<i data-lucide="cable"></i>有线 CAN
|
||||
</button>
|
||||
<button type="button" class="mode-option" data-control-transport="wireless_mqtt">
|
||||
<i data-lucide="wifi"></i>无线 CAN
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-group simulation-fault-field" id="simulationFaultField" hidden>
|
||||
<label class="field-label" for="simulationFault">故障注入场景</label>
|
||||
<select id="simulationFault" aria-label="选择模拟硬件故障">
|
||||
<option value="normal">正常反馈</option>
|
||||
<option value="feedback_timeout">反馈超时</option>
|
||||
<option value="adapter_disconnect">适配器断开</option>
|
||||
<option value="motor_stall">电机堵转</option>
|
||||
<option value="firmware_mismatch">固件不匹配</option>
|
||||
<option value="partial_feedback">部分反馈</option>
|
||||
</select>
|
||||
<p class="provider-note">仅生成模拟 HIL 结果,不会打开物理 CAN 适配器或调用 IAR。</p>
|
||||
</div>
|
||||
|
||||
<div class="field-group vehicle-field">
|
||||
<label class="field-label" for="vehicleSelect">车型配置</label>
|
||||
<div class="vehicle-select-control">
|
||||
<i data-lucide="car-front"></i>
|
||||
<select id="vehicleSelect" name="vehicle_id" aria-label="选择车型配置">
|
||||
<option value="">加载车型配置</option>
|
||||
</select>
|
||||
</div>
|
||||
<p class="provider-note" id="vehicleNote">正在读取座椅量程与角度标定</p>
|
||||
</div>
|
||||
|
||||
<button class="primary-action" id="runButton" type="submit" aria-label="发送消息">
|
||||
<i data-lucide="send-horizontal"></i>
|
||||
<span>发送</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</aside>
|
||||
|
||||
<main class="main-panel">
|
||||
<section class="request-header">
|
||||
<div>
|
||||
<div class="request-title-row">
|
||||
<h2 id="resultTitle">等待请求</h2>
|
||||
<span class="run-status idle" id="runStatus">IDLE</span>
|
||||
</div>
|
||||
<p id="requestMeta">Cabin / reference_vehicle / dry-run</p>
|
||||
</div>
|
||||
<div class="matrix-stat">
|
||||
<i data-lucide="table-properties"></i>
|
||||
<div><strong id="matrixCount">10 / 57</strong><span>报文 / 信号</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="cabin-band" aria-labelledby="cabinStateTitle">
|
||||
<div class="section-title-row cabin-heading">
|
||||
<div class="cabin-title-copy">
|
||||
<h3 id="cabinStateTitle">整车设备状态</h3>
|
||||
</div>
|
||||
<div class="cabin-channel-legend" aria-label="整车设备通道说明">
|
||||
<span data-channel="ch0"><i>CH0</i>副驾座椅</span>
|
||||
<span data-channel="ch1"><i>CH1</i>主驾 / 车身</span>
|
||||
<span class="simulation-label" id="feedbackModeLabel">MATRIX-BACKED · SIMULATED FEEDBACK</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="domain-grid">
|
||||
<article class="domain-panel climate-panel" data-domain-panel="climate" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="fan"></i></span>
|
||||
<div><strong>空调</strong><small>CMD 0x1DE · FB 0x404 · CH1</small></div>
|
||||
<span class="device-state" id="climatePower">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>电源 · 温度 · 风量/风向</p>
|
||||
<div class="climate-display">
|
||||
<div class="zone-temperature">
|
||||
<span>主驾</span><strong id="driverTemperature">--<sup>℃</sup></strong>
|
||||
<small>风向<b id="driverAirDirection">-</b></small>
|
||||
</div>
|
||||
<div class="airflow-visual" id="airflowVisual" data-level="0" aria-label="空调风量">
|
||||
<i></i><i></i><i></i><i></i><i></i><i></i><i></i>
|
||||
</div>
|
||||
<div class="zone-temperature passenger-temperature">
|
||||
<span>副驾</span><strong id="passengerTemperature">--<sup>℃</sup></strong>
|
||||
<small>风向<b id="passengerAirDirection">-</b></small>
|
||||
</div>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>风量</dt><dd id="fanLevel">未知</dd></div>
|
||||
<div><dt>循环</dt><dd id="cycleMode">未知</dd></div>
|
||||
<div><dt>A/C</dt><dd id="acState">未知</dd></div>
|
||||
<div><dt>前除霜</dt><dd id="defrostState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel vehicle-status-panel" data-domain-panel="vehicle_status" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="gauge"></i></span>
|
||||
<div><strong>车辆状态</strong><small>FB 0x342 · 0x4EB · 0x407 · CH1</small></div>
|
||||
<span class="device-state readonly-state" id="vehicleTelemetryState">只读</span>
|
||||
</header>
|
||||
<p class="domain-capability-line readonly-capability"><span>只读信号</span>档位 · 车速 · 四门锁状态</p>
|
||||
<div class="vehicle-primary-readout">
|
||||
<div><span>当前档位</span><strong id="vehicleGear">--</strong></div>
|
||||
<div><span>实时车速</span><strong id="vehicleSpeed">-- km/h</strong></div>
|
||||
</div>
|
||||
<div class="door-state-grid" aria-label="四门锁状态">
|
||||
<span data-door-state="front_left"><i></i>左前门<b id="frontLeftDoorState">未知</b></span>
|
||||
<span data-door-state="front_right"><i></i>右前门<b id="frontRightDoorState">未知</b></span>
|
||||
<span data-door-state="rear_left"><i></i>左后门<b id="rearLeftDoorState">未知</b></span>
|
||||
<span data-door-state="rear_right"><i></i>右后门<b id="rearRightDoorState">未知</b></span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel roof-panel" data-domain-panel="roof" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="panels-top-left"></i></span>
|
||||
<div><strong>遮阳帘</strong><small>CMD 0x431 · FB 0x41A · CH1</small></div>
|
||||
<span class="device-state" id="roofState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>开关 / 停止 · 位置</p>
|
||||
<div class="domain-summary-visual shade-summary-visual" aria-hidden="true">
|
||||
<span>闭合</span><i><b id="shadePositionMeter"></b></i><span>打开</span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>遮阳帘</dt><dd id="shadePositionValue">未知</dd></div>
|
||||
<div><dt>动作</dt><dd id="shadeMotionState">未知</dd></div>
|
||||
<div><dt>初始化</dt><dd id="shadeInitializedState">未知</dd></div>
|
||||
<div><dt>热保护</dt><dd id="shadeThermalState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel ambient-panel" data-domain-panel="ambient_light" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="lamp-ceiling"></i></span>
|
||||
<div><strong>车内灯光</strong><small>INTERIOR / AMBIENT · CH1</small></div>
|
||||
<span class="device-state" id="ambientPower">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>阅读灯 · 氛围亮度 · 色号</p>
|
||||
<div class="domain-summary-visual ambient-summary-visual" aria-hidden="true">
|
||||
<span data-ambient-zone="front"><i id="ambientFrontMeter"></i><b>前排</b></span>
|
||||
<span data-ambient-zone="rear"><i id="ambientRearMeter"></i><b>后排</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>氛围灯</dt><dd id="ambientMode">未知</dd></div>
|
||||
<div><dt>室内/阅读灯</dt><dd id="interiorLightPower">未知</dd></div>
|
||||
<div><dt>氛围亮度</dt><dd id="ambientBrightness">- / -</dd></div>
|
||||
<div><dt>氛围色号</dt><dd id="ambientColorIndex">- / -</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel window-panel" data-domain-panel="window" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="gallery-vertical-end"></i></span>
|
||||
<div><strong>车窗</strong><small>CMD 0x431 · FB 0x388 · CH1</small></div>
|
||||
<span class="device-state" id="windowState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>四窗独立 · 开/关/停 · 位置</p>
|
||||
<div class="domain-summary-visual window-summary-visual" aria-hidden="true">
|
||||
<span data-window-meter="front_left"><i></i><b>左前</b></span>
|
||||
<span data-window-meter="front_right"><i></i><b>右前</b></span>
|
||||
<span data-window-meter="rear_left"><i></i><b>左后</b></span>
|
||||
<span data-window-meter="rear_right"><i></i><b>右后</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>左前窗</dt><dd id="frontLeftWindow">未知</dd></div>
|
||||
<div><dt>右前窗</dt><dd id="frontRightWindow">未知</dd></div>
|
||||
<div><dt>左后窗</dt><dd id="rearLeftWindow">未知</dd></div>
|
||||
<div><dt>右后窗</dt><dd id="rearRightWindow">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="domain-panel lighting-panel" data-domain-panel="front_light" data-device-channel="ch1">
|
||||
<header>
|
||||
<span class="domain-icon"><i data-lucide="car-front"></i></span>
|
||||
<div><strong>前大灯</strong><small>FB 0x38A · CH1</small></div>
|
||||
<span class="device-state" id="frontLightState">UNKNOWN</span>
|
||||
</header>
|
||||
<p class="domain-capability-line"><span>可控参数</span>近/远光 · 雾灯 · 转向灯</p>
|
||||
<div class="domain-summary-visual lighting-summary-visual" aria-hidden="true">
|
||||
<span data-light-meter="lowBeamState"><i></i><b>近光</b></span>
|
||||
<span data-light-meter="highBeamState"><i></i><b>远光</b></span>
|
||||
<span data-light-meter="fogLightState"><i></i><b>雾灯</b></span>
|
||||
<span data-light-meter="turnSignalState"><i></i><b>转向</b></span>
|
||||
</div>
|
||||
<dl class="domain-readouts">
|
||||
<div><dt>近光灯</dt><dd id="lowBeamState">未知</dd></div>
|
||||
<div><dt>远光灯</dt><dd id="highBeamState">未知</dd></div>
|
||||
<div><dt>雾灯</dt><dd id="fogLightState">未知</dd></div>
|
||||
<div><dt>转向灯</dt><dd id="turnSignalState">未知</dd></div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="twin-band" aria-labelledby="twinTitle">
|
||||
<div class="twin-heading">
|
||||
<div class="twin-title-copy">
|
||||
<h3 id="twinTitle">整车孪生界面</h3>
|
||||
<div class="preset-indicator" id="presetIndicator" hidden>
|
||||
<i data-lucide="bookmark-check"></i><span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="twin-toolbar">
|
||||
<div class="twin-selector" role="tablist" aria-label="前排座椅孪生选择">
|
||||
<button class="twin-seat-button active" type="button" role="tab" aria-selected="true" data-twin-seat="passenger">副驾座椅 · CH0</button>
|
||||
<button class="twin-seat-button" type="button" role="tab" aria-selected="false" data-twin-seat="driver">主驾座椅 · CH1</button>
|
||||
</div>
|
||||
<button class="icon-button twin-reset" id="twinReset" type="button" title="复位 3D 视角" aria-label="复位 3D 视角">
|
||||
<i data-lucide="rotate-3d"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="twin-stage" id="seatTwin" data-selected-seat="passenger">
|
||||
<canvas id="seatTwinCanvas" data-vehicle-model="byd-tang-2024-reference" data-seat-layout="five-seat" data-active-seat="front-right" aria-label="通过模型动画展示 CH0 副驾座椅与 CH1 主驾座椅、空调气流、智能天幕、阅读灯、四门车窗与前大灯状态的整车数字孪生"></canvas>
|
||||
<div class="twin-seat-label passenger"><i></i><span>副驾座椅</span><small>CH0 · 右前排</small></div>
|
||||
<div class="twin-seat-label driver"><i></i><span>主驾座椅</span><small>CH1 · 左前排</small></div>
|
||||
<div class="twin-orientation"><i data-lucide="move-down"></i><span>车头方向</span></div>
|
||||
<aside class="twin-readout" aria-live="polite">
|
||||
<div class="twin-readout-heading">
|
||||
<div><strong id="twinSeatName">副驾座椅</strong><span id="twinSeatZone">CH0 · 右前排</span></div>
|
||||
<span class="twin-feedback"><i></i>FEEDBACK</span>
|
||||
</div>
|
||||
<div class="twin-axis-list">
|
||||
<div data-twin-axis="longitudinal"><span>水平位置</span><b>50%</b><i></i></div>
|
||||
<div data-twin-axis="cushion_tilt"><span>坐垫俯仰</span><b>50%</b><i></i></div>
|
||||
<div data-twin-axis="height"><span>座椅高度</span><b>50%</b><i></i></div>
|
||||
<div data-twin-axis="backrest_recline"><span>靠背夹角</span><b>20% · 104°</b><i></i></div>
|
||||
<div data-twin-axis="legrest_height"><span>腿托夹角</span><b>0% · 95°</b><i></i></div>
|
||||
<div data-twin-axis="legrest_extension"><span>腿托伸缩</span><b>0%</b><i></i></div>
|
||||
</div>
|
||||
<div class="twin-thermal-row" aria-label="座椅热舒适状态">
|
||||
<span data-twin-function="ventilation" data-level="0">
|
||||
<i data-lucide="wind"></i>
|
||||
<span class="twin-thermal-copy"><small>通风</small><b>关闭</b></span>
|
||||
<span class="twin-level-meter" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
</span>
|
||||
<span data-twin-function="heating" data-level="0">
|
||||
<i data-lucide="flame"></i>
|
||||
<span class="twin-thermal-copy"><small>加热</small><b>关闭</b></span>
|
||||
<span class="twin-level-meter" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||
</span>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="twin-fallback" id="twinFallback"><i data-lucide="loader-circle"></i><span>正在加载整车参考模型</span></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="state-band">
|
||||
<div class="seat-state-section">
|
||||
<div class="section-title-row">
|
||||
<div><h3>前排座椅状态</h3></div>
|
||||
<span class="simulation-label" id="seatFeedbackModeLabel">CH0 / CH1 FEEDBACK</span>
|
||||
</div>
|
||||
<div class="seat-grid">
|
||||
<article class="seat-card" data-seat="passenger">
|
||||
<div class="seat-identity"><i data-lucide="armchair"></i><div><strong>副驾座椅</strong><span>右前排</span></div></div>
|
||||
<span class="seat-channel-badge" data-seat-feedback="passenger">CH0 · 等待反馈</span>
|
||||
<div class="dynamic-mode-state" data-dynamic-mode>
|
||||
<i data-lucide="waves"></i><span>动态模式</span><b>未运行</b>
|
||||
</div>
|
||||
<div class="motion-state-grid" aria-live="polite"></div>
|
||||
</article>
|
||||
<article class="seat-card" data-seat="driver">
|
||||
<div class="seat-identity"><i data-lucide="armchair"></i><div><strong>主驾座椅</strong><span>左前排</span></div></div>
|
||||
<span class="seat-channel-badge" data-seat-feedback="driver">CH1 · 等待反馈</span>
|
||||
<div class="dynamic-mode-state" data-dynamic-mode>
|
||||
<i data-lucide="radio-tower"></i><span>车端 CAN FD</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>
|
||||
<span data-function="massage"><i data-lucide="waves"></i>按摩 <b>--</b></span>
|
||||
</div>
|
||||
<div class="motion-state-grid" aria-live="polite"></div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
<div class="coverage-section">
|
||||
<h3>当前控制边界</h3>
|
||||
<dl>
|
||||
<div><dt>空调 18–32℃ / 0–7 档</dt><dd class="available">可编译</dd></div>
|
||||
<div><dt>天幕 / 遮阳帘 0–100%</dt><dd class="available">可编译</dd></div>
|
||||
<div><dt>氛围灯 RGB / 亮度</dt><dd class="available">可编译</dd></div>
|
||||
<div><dt>副驾座椅统一执行器</dt><dd class="simulated">t2g_primary_seat</dd></div>
|
||||
<div><dt>主驾座椅车端执行器</dt><dd class="simulated">CH1 · 左前排</dd></div>
|
||||
<div><dt>真实台架 CAN 下发</dt><dd class="blocked" id="hardwareCoverage">未配置</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail-section result-workspace" aria-labelledby="resultWorkspaceTitle">
|
||||
<header class="result-workspace-heading">
|
||||
<div><h3 id="resultWorkspaceTitle">编排结果工作台</h3></div>
|
||||
<span class="result-workspace-badge"><i data-lucide="route"></i>REQUEST → CONFIG → CAN → RESULT</span>
|
||||
</header>
|
||||
<nav class="tabs result-nav" aria-label="结果快速导航">
|
||||
<button class="tab active" type="button" data-tab="trace"><span class="tab-index">01</span><i data-lucide="list-tree"></i><span class="tab-copy"><strong>决策轨迹</strong><small id="traceNavMeta">等待请求</small></span><i data-lucide="arrow-down"></i></button>
|
||||
<button class="tab" type="button" data-tab="config"><span class="tab-index">02</span><i data-lucide="layout-grid"></i><span class="tab-copy"><strong>配置卡片</strong><small id="configNavMeta">等待生成</small></span><i data-lucide="arrow-down"></i></button>
|
||||
<button class="tab" type="button" data-tab="plan"><span class="tab-index">03</span><i data-lucide="binary"></i><span class="tab-copy"><strong>CAN 计划</strong><small id="planNavMeta">等待编译</small></span><i data-lucide="arrow-down"></i></button>
|
||||
<button class="tab" type="button" data-tab="result"><span class="tab-index">04</span><i data-lucide="circle-check-big"></i><span class="tab-copy"><strong>执行结果</strong><small id="resultNavMeta">尚未执行</small></span><i data-lucide="arrow-down"></i></button>
|
||||
</nav>
|
||||
|
||||
<div class="tab-panel active" id="panel-trace" role="tabpanel">
|
||||
<div class="workspace-panel-title"><div><h3>决策轨迹与校验</h3></div><span class="workspace-panel-state" id="tracePanelState">IDLE</span></div>
|
||||
<div class="trace-layout">
|
||||
<div>
|
||||
<div class="detail-heading"><h3>可审计决策摘要</h3><span id="providerUsed">--</span></div>
|
||||
<ol class="decision-list" id="decisionList"><li>提交请求后显示识别目标、能力选择和约束来源。</li></ol>
|
||||
</div>
|
||||
<div>
|
||||
<div class="detail-heading"><h3>阶段事件</h3><span id="traceDuration">0 ms</span></div>
|
||||
<div class="trace-list" id="traceList"><div class="empty-line">暂无执行事件</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="validation-block">
|
||||
<div class="detail-heading"><h3>校验结果</h3><span id="validationCount">0 条</span></div>
|
||||
<div id="validationList" class="validation-list"><div class="empty-line">等待配置生成</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel config-panel" id="panel-config" role="tabpanel">
|
||||
<div class="config-panel-heading">
|
||||
<div><h3>指令配置与硬件映射</h3></div>
|
||||
<div class="config-actions"><button class="config-action" type="button" data-copy="config"><i data-lucide="copy"></i>复制 YAML</button><button class="config-action" type="button" data-download="config"><i data-lucide="download"></i>下载配置</button></div>
|
||||
</div>
|
||||
<div class="config-card-grid" id="configCards">
|
||||
<div class="config-empty"><i data-lucide="panel-top-dashed"></i><strong>等待生成配置</strong><span>提交车舱控制指令后,这里会显示控制对象、目标参数和硬件信号。</span></div>
|
||||
</div>
|
||||
<details class="config-source-details">
|
||||
<summary><span><i data-lucide="braces"></i>查看原始配置</span><small id="configFileName">functions/seat.yaml</small></summary>
|
||||
<pre id="configCode"><code># 等待生成 FunctionConfig</code></pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel plan-panel" id="panel-plan" role="tabpanel">
|
||||
<div class="workspace-panel-title plan-panel-heading"><div><h3>CAN 编译计划</h3></div><button class="config-action" type="button" data-copy="plan"><i data-lucide="copy"></i>复制计划</button></div>
|
||||
<div class="frame-summary" id="frameSummary"><span>Frame ID</span><b>--</b><span>Payload</span><code>--</code><span>Mode</span><b>DRY-RUN</b></div>
|
||||
<details class="config-source-details plan-source-details">
|
||||
<summary><span><i data-lucide="binary"></i>查看原始 CAN 计划</span><small id="planFileName">command-plans/seat.yaml</small></summary>
|
||||
<pre id="planCode"><code># 等待 CAN 编译</code></pre>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div class="tab-panel result-panel" id="panel-result" role="tabpanel">
|
||||
<div class="workspace-panel-title"><div><h3>执行结果与硬件回读</h3></div><span class="workspace-panel-state" id="resultPanelState">IDLE</span></div>
|
||||
<div class="execution-summary" id="executionSummary">
|
||||
<i data-lucide="circle-dashed"></i><div><strong>尚未执行</strong><span>运行结果将在此处显示</span></div>
|
||||
</div>
|
||||
<div class="result-list" id="resultList"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<aside class="gesture-drawer" id="gestureDrawer" aria-hidden="true" aria-labelledby="gestureDrawerTitle">
|
||||
<header class="gesture-drawer-header">
|
||||
<div>
|
||||
<h2 id="gestureDrawerTitle">手势控制</h2>
|
||||
</div>
|
||||
<button class="gesture-close" id="gestureCloseButton" type="button" aria-label="关闭手势控制"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
|
||||
<div class="gesture-status-strip">
|
||||
<span class="gesture-state-pill idle" id="gestureStatePill"><i></i><b>未启动</b></span>
|
||||
<span id="gestureSafetyText">摄像头尚未开启</span>
|
||||
</div>
|
||||
|
||||
<div class="gesture-camera-stage">
|
||||
<video id="gestureVideo" playsinline muted></video>
|
||||
<canvas id="gestureCanvas"></canvas>
|
||||
<div class="gesture-camera-empty" id="gestureCameraEmpty">
|
||||
<span><i data-lucide="scan-face"></i></span>
|
||||
<strong>用手势控制副驾驶座椅</strong>
|
||||
<p>画面只在本机处理,不上传视频。</p>
|
||||
</div>
|
||||
<div class="gesture-live-badge" id="gestureLiveBadge" hidden><i></i>LIVE</div>
|
||||
</div>
|
||||
|
||||
<div class="gesture-now">
|
||||
<div><span>当前手势</span><strong id="gestureDetected">—</strong></div>
|
||||
<i data-lucide="arrow-right"></i>
|
||||
<div><span>期望动作</span><strong id="gestureExpected">等待启动</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="gesture-progress"><i id="gestureProgressBar"></i></div>
|
||||
|
||||
<div class="gesture-actions">
|
||||
<button class="gesture-primary" id="gestureStartButton" type="button"><i data-lucide="camera"></i><span>开启摄像头</span></button>
|
||||
<button id="gestureStopButton" type="button" disabled><i data-lucide="octagon"></i>停止</button>
|
||||
</div>
|
||||
|
||||
<section class="gesture-guide-card">
|
||||
<div class="gesture-card-heading"><div><span>手势速查</span><small>张掌激活 · 握拳只停止</small></div><button id="gestureCustomizeButton" type="button"><i data-lucide="sliders-horizontal"></i>自定义</button></div>
|
||||
<div class="gesture-map-grid" id="gestureMapGrid"></div>
|
||||
<div class="gesture-customize" id="gestureCustomizePanel" hidden>
|
||||
<div><span>识别到</span><strong id="gestureCustomizeToken">请先摆出手势</strong></div>
|
||||
<select id="gestureActionSelect" aria-label="选择座椅动作"></select>
|
||||
<button id="gestureBindButton" type="button">绑定当前手势</button>
|
||||
<button id="gestureResetMapping" type="button">恢复默认</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="gesture-footnote"><i data-lucide="shield-check"></i><span>换动作无需重新张掌;连续 3 秒无有效动作后自动锁止。</span></footer>
|
||||
</aside>
|
||||
|
||||
<div class="mode-gallery-backdrop" id="modeGalleryBackdrop" hidden>
|
||||
<section class="mode-gallery" id="modeGallery" role="dialog" aria-modal="true" aria-labelledby="modeGalleryTitle">
|
||||
<header class="mode-gallery-header">
|
||||
<div>
|
||||
<h2 id="modeGalleryTitle">管理功能模式</h2>
|
||||
<p>按来源与运行方式整理,快速定位并管理模式</p>
|
||||
</div>
|
||||
<button class="mode-gallery-close" id="modeGalleryClose" type="button" aria-label="关闭功能模式管理"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
<div class="mode-gallery-summary"><span><i data-lucide="layers-3"></i>自定义模式 · 静态模式 · 动态模式</span><strong id="modeGallerySummaryCount">0</strong></div>
|
||||
<div class="mode-gallery-groups" id="modeGalleryGroups" aria-live="polite"></div>
|
||||
<footer class="mode-gallery-footer"><i data-lucide="info"></i><span>自定义模式支持编辑与删除;静态模式执行后保持目标状态;动态模式持续变化,可随时停止。</span></footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="mode-editor-backdrop" id="modeEditorBackdrop" hidden>
|
||||
<section class="mode-editor" role="dialog" aria-modal="true" aria-labelledby="modeEditorTitle">
|
||||
<div class="mode-editor-setup">
|
||||
<header class="mode-editor-header">
|
||||
<div>
|
||||
<h2 id="modeEditorTitle">编辑功能模式</h2>
|
||||
</div>
|
||||
<button id="modeEditorClose" type="button" aria-label="关闭模式编辑器"><i data-lucide="x"></i></button>
|
||||
</header>
|
||||
<div class="mode-editor-inputs">
|
||||
<label class="mode-editor-field mode-editor-name-field" for="modeEditorName">
|
||||
<span>模式名称</span>
|
||||
<input id="modeEditorName" maxlength="64" autocomplete="off" placeholder="输入模式名称" />
|
||||
</label>
|
||||
<label class="mode-editor-field mode-editor-instruction-field" for="modeEditorInstruction">
|
||||
<span>自然语言指令</span>
|
||||
<textarea id="modeEditorInstruction" maxlength="1000" rows="3" placeholder="例如:空调24度、风量2档,打开遮阳帘"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<section class="mode-editor-flow" aria-live="polite">
|
||||
<header>
|
||||
<span><i data-lucide="workflow"></i>控制指令流</span>
|
||||
<div class="mode-editor-flow-tools">
|
||||
<b id="modeEditorFlowCount">尚未生成</b>
|
||||
<button id="modeEditorFlowExpand" type="button" aria-expanded="false" aria-label="展开控制指令区域" title="展开控制指令区域">
|
||||
<i data-lucide="maximize-2"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="mode-editor-flow-preview empty" id="modeEditorFlowPreview">
|
||||
<p>点击“生成指令”后,这里会显示由后端解析、校验并编译的控制指令流。</p>
|
||||
</div>
|
||||
</section>
|
||||
<p class="mode-editor-status" id="modeEditorStatus">修改自然语言后需要重新生成指令,才能点击完成永久保存。</p>
|
||||
<footer class="mode-editor-actions">
|
||||
<button id="modeEditorCancel" type="button">取消</button>
|
||||
<button class="mode-editor-execute" id="modeEditorExecute" type="button"><i data-lucide="wand-sparkles" data-icon="inline-start"></i><span>生成指令</span></button>
|
||||
<button class="mode-editor-complete" id="modeEditorComplete" type="button" disabled><i data-lucide="save" data-icon="inline-start"></i><span>完成</span></button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<button class="glass-theme-toggle" id="glassThemeToggle" type="button" aria-label="切换到白日模式" aria-pressed="true" title="切换到白日模式">
|
||||
<i data-lucide="sun" data-theme-icon="sun" aria-hidden="true"></i>
|
||||
<i data-lucide="moon" data-theme-icon="moon" aria-hidden="true"></i>
|
||||
<span class="visually-hidden">切换白日/夜间模式</span>
|
||||
</button>
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
</template>
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createApp, nextTick } from "vue";
|
||||
|
||||
import App from "./App.vue";
|
||||
import "../styles.css";
|
||||
import "../console-v2.css";
|
||||
import "../self-extension.css";
|
||||
import "../speech-input.css";
|
||||
import "../assistant-chat.css";
|
||||
import "../energy-monitor.css";
|
||||
import "../ui-consistency.css";
|
||||
import "../codepen-glass-theme.css";
|
||||
import "./styles/design-system.css";
|
||||
import "../vue-ui.js";
|
||||
|
||||
const application = createApp(App);
|
||||
application.config.errorHandler = (error, instance, info) => {
|
||||
console.error("N7 Vue application error", { error, instance, info });
|
||||
};
|
||||
application.mount("#app");
|
||||
|
||||
document.querySelector("#app")?.removeAttribute("aria-busy");
|
||||
document.documentElement.classList.add("vue-vite-application");
|
||||
document.documentElement.dataset.vueVersion = application.version || "3";
|
||||
|
||||
nextTick(() => {
|
||||
window.lucide?.createIcons({ attrs: { "aria-hidden": "true" } });
|
||||
window.dispatchEvent(new CustomEvent("cognitive-agent:vue-mounted"));
|
||||
});
|
||||
|
||||
window.CognitiveAgentVueApp = Object.freeze({
|
||||
application,
|
||||
version: application.version || "3"
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
/* N7 Web Console design-system contract.
|
||||
* Loaded last by Vite so every legacy surface consumes one geometry,
|
||||
* typography, state and elevation scale while the runtime is migrated to
|
||||
* Vue components incrementally. */
|
||||
|
||||
:root {
|
||||
--n7-control-height-sm: 32px;
|
||||
--n7-control-height: 38px;
|
||||
--n7-icon-button-size: 38px;
|
||||
--n7-radius-control: 10px;
|
||||
--n7-radius-card: 14px;
|
||||
--n7-radius-section: 16px;
|
||||
--n7-radius-panel: 18px;
|
||||
--n7-radius-dialog: 22px;
|
||||
--n7-font-caption: 12px;
|
||||
--n7-font-body: 13px;
|
||||
--n7-font-card-title: 14px;
|
||||
--n7-font-panel-title: 16px;
|
||||
--n7-font-dialog-title: 22px;
|
||||
--n7-shadow-card: inset 0 1px 0 var(--glass-highlight), 0 7px 20px color-mix(in oklch, var(--foreground) 8%, transparent);
|
||||
--n7-shadow-panel: inset 0 1px 0 var(--glass-highlight), 0 14px 34px color-mix(in oklch, var(--foreground) 13%, transparent);
|
||||
--n7-shadow-dialog: inset 0 1px 0 var(--glass-highlight), 0 28px 84px color-mix(in oklch, var(--foreground) 28%, transparent);
|
||||
}
|
||||
|
||||
#app {
|
||||
min-width: inherit;
|
||||
min-height: 100dvh;
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* Workspace panels: one top-level geometry, distinct from their children. */
|
||||
body.console-v2.v2-passive-display.v2-interactive-sync :is(
|
||||
.main-panel > .v2-mode-rail,
|
||||
#assistantDrawer,
|
||||
.joint-twin-shell > .twin-band,
|
||||
#v2DevicePanel,
|
||||
#v2ExtensionPanel .v2-extension-sync-mount
|
||||
) {
|
||||
border-radius: var(--n7-radius-panel) !important;
|
||||
box-shadow: var(--n7-shadow-panel) !important;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.v2-sync-transport-field,
|
||||
.v2-sync-execution-field,
|
||||
.v2-can-command-panel,
|
||||
.v2-nfc-inline,
|
||||
.v2-extension-gateway,
|
||||
.v2-extension-synced-device,
|
||||
.v2-extension-empty,
|
||||
.v2-pet-card,
|
||||
.v2-pet-presence-card,
|
||||
.v2-device-card,
|
||||
.v2-synced-device,
|
||||
.mode-gallery-card,
|
||||
.mode-editor-command-card,
|
||||
.energy-kpi,
|
||||
.energy-device-row,
|
||||
.assistant-dispatched-controls,
|
||||
.assistant-mode-draft,
|
||||
.assistant-mode-created,
|
||||
.assistant-control-suggestion
|
||||
) {
|
||||
border-radius: var(--n7-radius-card) !important;
|
||||
box-shadow: var(--n7-shadow-card) !important;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.v2-sync-log,
|
||||
.v2-pet-card-region,
|
||||
.mode-gallery-group,
|
||||
.mode-editor-flow,
|
||||
.v2-nfc-stage,
|
||||
.energy-trend-card,
|
||||
.energy-devices-card
|
||||
) {
|
||||
border-radius: var(--n7-radius-section) !important;
|
||||
}
|
||||
|
||||
/* Only actionable cards float. Read-only panels remain spatially stable. */
|
||||
body.console-v2 :is(
|
||||
.v2-sync-log,
|
||||
.v2-pet-card-region,
|
||||
.mode-editor-flow,
|
||||
.energy-trend-card,
|
||||
.energy-devices-card
|
||||
):is(:hover, :focus-within) {
|
||||
transform: none !important;
|
||||
box-shadow: var(--n7-shadow-card) !important;
|
||||
}
|
||||
|
||||
/* The current DOM uses .twin-selector; the previous glass selector targeted
|
||||
* the removed .twin-seat-switch class and produced white-on-white tabs. */
|
||||
body.console-v2 .joint-twin-shell :is(.twin-selector, .twin-selector button, .twin-reset, .twin-focus-badge) {
|
||||
color: var(--muted-foreground) !important;
|
||||
background: var(--glass-card) !important;
|
||||
border-color: var(--glass-line) !important;
|
||||
box-shadow: inset 0 1px 0 var(--glass-highlight) !important;
|
||||
}
|
||||
|
||||
body.console-v2 .joint-twin-shell .twin-selector button.active {
|
||||
color: var(--primary-foreground) !important;
|
||||
background: var(--primary) !important;
|
||||
border-color: color-mix(in oklch, var(--primary) 72%, white 18%) !important;
|
||||
}
|
||||
|
||||
/* Shared heading hierarchy. */
|
||||
body.console-v2 :is(
|
||||
#assistantDrawer h2,
|
||||
.joint-twin-shell .twin-heading h3,
|
||||
.v2-device-sync-board > header h3,
|
||||
.v2-extension-sync-heading h3,
|
||||
.v2-mode-rail .mode-rail-title
|
||||
) {
|
||||
font-size: var(--n7-font-panel-title) !important;
|
||||
font-weight: 750 !important;
|
||||
line-height: 1.25 !important;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
/* The log title shares a 217 px header with its filter, so it needs the
|
||||
* compact card-title scale instead of the full panel-title scale. */
|
||||
body.console-v2 .v2-sync-log h3 {
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: var(--n7-font-card-title) !important;
|
||||
font-weight: 750 !important;
|
||||
line-height: 1.25 !important;
|
||||
letter-spacing: -0.02em;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.mode-gallery-header h2,
|
||||
.mode-editor-header h2,
|
||||
.v2-extension-dialog > header h2,
|
||||
.v2-extension-dialog form > header h2,
|
||||
.energy-monitor-heading h2
|
||||
) {
|
||||
font-size: var(--n7-font-dialog-title) !important;
|
||||
font-weight: 750 !important;
|
||||
line-height: 1.2 !important;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.mode-gallery-card-heading strong,
|
||||
.v2-device-card-focus strong,
|
||||
.v2-nfc-inline strong,
|
||||
.v2-extension-gateway strong,
|
||||
.v2-pet-card > header > strong,
|
||||
.energy-trend-card h3,
|
||||
.energy-devices-card h3
|
||||
) {
|
||||
font-size: var(--n7-font-card-title) !important;
|
||||
line-height: 1.35 !important;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.mode-editor-status,
|
||||
.mode-gallery-footer,
|
||||
.v2-pet-settings-footnote,
|
||||
.v2-pet-card-fields > label > span,
|
||||
.v2-pet-nfc-field output,
|
||||
.energy-kpi > small,
|
||||
.energy-kpi > em,
|
||||
.energy-devices-card > footer
|
||||
) {
|
||||
font-size: var(--n7-font-caption) !important;
|
||||
line-height: 1.45 !important;
|
||||
}
|
||||
|
||||
/* One dialog radius, elevation and close-button size. */
|
||||
body.console-v2 :is(
|
||||
.mode-gallery,
|
||||
.mode-editor,
|
||||
.v2-nfc-dialog,
|
||||
.v2-gscu-tcp-dialog,
|
||||
.v2-pet-settings-dialog,
|
||||
.energy-monitor-page
|
||||
) {
|
||||
border-radius: var(--n7-radius-dialog) !important;
|
||||
box-shadow: var(--n7-shadow-dialog) !important;
|
||||
}
|
||||
|
||||
body.console-v2 :is(
|
||||
.mode-gallery-close,
|
||||
.mode-editor-header > button,
|
||||
.v2-extension-dialog [data-dialog-close],
|
||||
.v2-extension-dialog [data-close-extension-dialog],
|
||||
.v2-extension-dialog [data-close-nfc-dialog],
|
||||
#energyMonitorClose
|
||||
) {
|
||||
width: var(--n7-icon-button-size) !important;
|
||||
height: var(--n7-icon-button-size) !important;
|
||||
min-width: var(--n7-icon-button-size);
|
||||
min-height: var(--n7-icon-button-size);
|
||||
border-radius: var(--n7-radius-control) !important;
|
||||
}
|
||||
|
||||
/* Compact icon actions still meet a usable hit target without becoming a
|
||||
* second visual system. */
|
||||
body.console-v2 :is(
|
||||
.twin-reset,
|
||||
.mode-gallery-edit,
|
||||
.mode-gallery-delete,
|
||||
.v2-pet-card > header > button
|
||||
) {
|
||||
width: var(--n7-control-height-sm) !important;
|
||||
height: var(--n7-control-height-sm) !important;
|
||||
min-width: var(--n7-control-height-sm);
|
||||
min-height: var(--n7-control-height-sm);
|
||||
border-radius: var(--n7-radius-control) !important;
|
||||
}
|
||||
|
||||
/* Offline hardware is a destructive state, not a primary/active state. */
|
||||
body.console-v2 .v2-nfc-stage:has(.v2-nfc-waiting.offline) > header {
|
||||
color: var(--status-error-foreground) !important;
|
||||
background: color-mix(in oklch, var(--destructive) 16%, var(--card)) !important;
|
||||
border-bottom: 1px solid color-mix(in oklch, var(--destructive) 32%, var(--border));
|
||||
}
|
||||
|
||||
body.console-v2 .v2-nfc-stage:has(.v2-nfc-waiting.offline) > header :is(span, small, h3, p, b) {
|
||||
color: var(--status-error-foreground) !important;
|
||||
}
|
||||
|
||||
/* Keep the global appearance control out of content and below every modal. */
|
||||
.glass-theme-toggle {
|
||||
left: max(18px, env(safe-area-inset-left)) !important;
|
||||
bottom: max(18px, env(safe-area-inset-bottom)) !important;
|
||||
z-index: 1100 !important;
|
||||
}
|
||||
|
||||
body.mode-gallery-open .glass-theme-toggle,
|
||||
body.mode-editor-open .glass-theme-toggle,
|
||||
body:has(.v2-extension-dialog[open]) .glass-theme-toggle,
|
||||
body:has(.energy-monitor-layer:not([hidden])) .glass-theme-toggle {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* The sidebar reserves the floating control's footprint instead of allowing
|
||||
* it to cover the log timeline. */
|
||||
body.console-v2 .v2-sync-log {
|
||||
margin-bottom: 58px;
|
||||
}
|
||||
|
||||
/* Top mode rail communicates that more content is available. */
|
||||
body.console-v2 .v2-mode-rail .mode-rail-content {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
body.console-v2 .v2-mode-rail .mode-rail-content::after {
|
||||
width: 28px;
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 0 0 auto;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--glass-panel) 94%, transparent));
|
||||
}
|
||||
|
||||
@media (max-width: 1360px) {
|
||||
.glass-theme-toggle {
|
||||
width: 42px !important;
|
||||
height: 42px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
body.console-v2 * {
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user