<!--
  SPEC: PJ2026-010405 云端控制台; PJ2026-010103 HWPOD服务。
  Implementation reference: draft-2026-07-13-p0-cloud-console.
-->
<script setup lang="ts">
import { Grid2X2, List, Rows3, Rows4 } from "lucide-vue-next";
import { computed, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import AsyncBoundary from "@/components/common/AsyncBoundary.vue";
import BaseDrawer from "@/components/common/BaseDrawer.vue";
import HwpodDevicesWorkspace from "@/components/hwpod/HwpodDevicesWorkspace.vue";
import HwpodNodesWorkspace from "@/components/hwpod/HwpodNodesWorkspace.vue";
import HwpodOnboardingWorkspace from "@/components/hwpod/HwpodOnboardingWorkspace.vue";
import HwpodOperationsWorkspace from "@/components/hwpod/HwpodOperationsWorkspace.vue";
import HwpodReadinessRail from "@/components/hwpod/HwpodReadinessRail.vue";
import BoundedWorkspace from "@/components/layout/BoundedWorkspace.vue";
import PageCommandBar from "@/components/layout/PageCommandBar.vue";
import StatusStrip from "@/components/layout/StatusStrip.vue";
import ViewModeSwitch from "@/components/console/ViewModeSwitch.vue";
import { useConsoleViewState } from "@/composables/useConsoleViewState";
import { useHwpodStore } from "@/stores/hwpod";
import type { HwpodDeviceTopologyItem, HwpodNodeReadinessStage } from "@/types";

type ConsoleTab = "devices" | "nodes" | "onboarding";

const route = useRoute();
const router = useRouter();
const hwpod = useHwpodStore();
const viewState = useConsoleViewState({ view: "cards", density: "comfortable", sort: "name" });
const searchText = ref(viewState.q.value);
const copied = ref(false);
const workspaceMode = computed(() => tab.value === "devices" && route.query.workspace === "1" && Boolean(text(route.params.hwpodId)));

const tab = computed<ConsoleTab>(() => {
  const value = route.path.split("/")[2] ?? "";
  return value === "nodes" || value === "onboarding" ? value : "devices";
});
const pathEntityId = computed(() => tab.value === "devices" ? text(route.params.hwpodId) : text(route.params.nodeId));
const effectiveQuery = computed(() => pathEntityId.value || viewState.q.value);
const effectiveCursor = computed(() => pathEntityId.value ? "" : viewState.cursor.value);
const view = viewState.view;
const density = viewState.density;
const devices = computed(() => hwpod.deviceTopology?.items ?? []);
const nodes = computed(() => hwpod.nodeTopology?.items ?? []);
const summary = computed(() => tab.value === "devices" ? hwpod.deviceTopology?.summary : hwpod.nodeTopology?.summary);
const invalidSpecs = computed(() => (tab.value === "devices" ? hwpod.deviceTopology : hwpod.nodeTopology)?.invalidSpecs ?? []);
const firstInvalidSpec = computed(() => invalidSpecs.value[0] ?? null);
const selectedDevice = computed(() => devices.value.find((device) => device.hwpodId === text(route.params.hwpodId)) ?? null);
const selectedNodeByPath = computed(() => nodes.value.find((node) => node.nodeId === text(route.params.nodeId)) ?? null);
const selectedNode = computed(() => selectedNodeByPath.value);
const selectedNodeDetail = computed(() => tab.value === "nodes" ? selectedNodeByPath.value : null);
const onboardingStages = computed<HwpodNodeReadinessStage[]>(() => selectedNode.value?.readiness ?? (hwpod.nodeUpdate?.readinessStages ?? []).map((stage) => ({ ...stage, status: "unknown", blocker: null })));
const artifact = computed(() => hwpod.nodeUpdate?.artifact ?? null);
const sortField = computed(() => viewState.sort.value.replace(/^-/, "") || "name");
const sortDirection = computed<"asc" | "desc">(() => viewState.sort.value.startsWith("-") ? "desc" : "asc");
const hasContent = computed(() => tab.value === "devices" ? devices.value.length > 0 : tab.value === "nodes" ? nodes.value.length > 0 : Boolean(hwpod.nodeUpdate) || nodes.value.length > 0);
const boundaryState = computed(() => {
  if (hwpod.consoleError) return hwpod.consoleStale && hasContent.value ? "partial" as const : "error" as const;
  if (hwpod.consoleLoading) return hasContent.value ? "refreshing" as const : "initial-loading" as const;
  if (invalidSpecs.value.length > 0) return "partial" as const;
  if (!hasContent.value) return "empty" as const;
  return "ready" as const;
});
const statusItems = computed(() => [
  { id: "owner", label: "owner", value: "cloud-api-hwpod-domain", tone: "info" as const },
  { id: "nodes", label: "nodes online", value: `${summary.value?.onlineNodeCount ?? 0}/${summary.value?.nodeCount ?? 0}`, tone: (summary.value?.offlineNodeCount ?? 0) > 0 ? "warn" as const : "ok" as const },
  { id: "hwpods", label: "HWPOD available", value: `${summary.value?.availableDeviceCount ?? 0}/${summary.value?.deviceCount ?? 0}`, tone: (summary.value?.busyDeviceCount ?? 0) > 0 ? "warn" as const : "neutral" as const },
  { id: "invalid", label: "invalid specs", value: summary.value?.invalidSpecCount ?? 0, tone: (summary.value?.invalidSpecCount ?? 0) > 0 ? "error" as const : "neutral" as const }
]);
const statusFilterOptions = computed(() => tab.value === "nodes"
  ? [
      { value: "online", label: "在线" },
      { value: "offline", label: "离线" }
    ]
  : [
      { value: "online", label: "在线" },
      { value: "offline", label: "离线" }
    ]);
const collectionPage = computed(() => tab.value === "devices" ? hwpod.deviceTopology?.page : tab.value === "nodes" ? hwpod.nodeTopology?.page : null);

watch(viewState.q, (value) => { searchText.value = value; });
watch([tab, effectiveQuery, viewState.filter, viewState.sort, effectiveCursor], ([currentTab, q, filter, sort, cursor]) => {
  const resource = currentTab === "onboarding" ? "onboarding" : currentTab === "nodes" ? "node" : "device";
  void hwpod.refreshConsole(resource, {
    q,
    status: filter,
    sort: (sort.replace(/^-/, "") || "name") as "name" | "status" | "lastSeenAt" | "deviceCount",
    direction: sort.startsWith("-") ? "desc" : "asc",
    cursor
  });
}, { immediate: true });

function goTab(next: ConsoleTab): void {
  const query = collectionQuery();
  delete query.cursor;
  delete query.filter;
  if (next === "onboarding") {
    delete query.q;
    delete query.sort;
  }
  void router.push({ path: `/hwpods/${next}`, query });
}

function setView(next: "cards" | "list"): void {
  void viewState.update({ view: next });
}

function setDensity(next: "compact" | "comfortable"): void {
  void viewState.update({ density: next });
}

function applySearch(): void {
  void viewState.update({ q: searchText.value.trim() });
}

function setStatusFilter(event: Event): void {
  void viewState.update({ filter: (event.target as HTMLSelectElement).value });
}

function toggleSort(key: string): void {
  const next = sortField.value === key && sortDirection.value === "asc" ? `-${key}` : key;
  void viewState.update({ sort: next });
}

function selectDevice(id: string): void {
  void router.replace({ name: "HwpodDevices", params: { hwpodId: id }, query: collectionQuery() });
}

function closeInspector(): void {
  void router.replace({ name: "HwpodDevices", query: collectionQuery() });
}

function openWorkspace(): void {
  if (!selectedDevice.value) return;
  void router.replace({ name: "HwpodDevices", params: { hwpodId: selectedDevice.value.hwpodId }, query: { workspace: "1", path: "." } });
}

function closeWorkspace(): void {
  void router.replace({ name: "HwpodDevices", params: { hwpodId: text(route.params.hwpodId) } });
}

function navigateWorkspace(path: string): void {
  void router.replace({ name: "HwpodDevices", params: { hwpodId: text(route.params.hwpodId) }, query: { workspace: "1", path } });
}

function selectDeviceItem(device: HwpodDeviceTopologyItem): void {
  selectDevice(device.hwpodId);
}

function selectNodeItem(node: { nodeId: string }): void {
  void router.replace({ name: "HwpodNodes", params: { nodeId: node.nodeId }, query: collectionQuery() });
}

function closeNodeInspector(): void {
  void router.replace({ name: "HwpodNodes", query: collectionQuery() });
}

function setCursor(cursor: string | null): void {
  void viewState.update({ cursor: cursor || "" });
}

function refreshCurrent(): void {
  const resource = tab.value === "onboarding" ? "onboarding" : tab.value === "nodes" ? "node" : "device";
  void hwpod.refreshConsole(resource, { q: effectiveQuery.value, status: viewState.filter.value, sort: sortField.value as "name" | "status" | "lastSeenAt" | "deviceCount", direction: sortDirection.value, cursor: effectiveCursor.value });
}

function probeCurrent(): void {
  const resource = tab.value === "nodes" ? "node" : "device";
  void hwpod.refreshConsole(resource, { q: effectiveQuery.value, status: viewState.filter.value, sort: sortField.value as "name" | "status" | "lastSeenAt" | "deviceCount", direction: sortDirection.value, cursor: effectiveCursor.value, probe: true });
}

function chooseNodeId(nodeId: string): void {
  void router.replace({ name: "HwpodOnboarding", params: nodeId ? { nodeId } : {}, query: collectionQuery() });
}

function collectionQuery() {
  const query = { ...route.query };
  for (const key of Object.keys(query)) if (!["view", "density", "q", "filter", "sort", "cursor"].includes(key)) delete query[key];
  return query;
}

async function copySha(): Promise<void> {
  const sha = artifact.value?.sha256;
  if (!sha) return;
  await navigator.clipboard.writeText(sha);
  copied.value = true;
  window.setTimeout(() => { copied.value = false; }, 1600);
}

function statusLabel(status: string): string {
  return ({ online: "在线", offline: "离线" }[status] ?? status);
}

function formatTime(value: string | null): string {
  if (!value) return "未观测";
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN", { hour12: false });
}

function text(value: unknown): string {
  return typeof value === "string" ? value.trim() : "";
}
</script>

<template>
  <section class="hwpod-console" data-testid="hwpod-console" :data-workspace-mode="workspaceMode ? 'true' : 'false'">
    <PageCommandBar v-if="!workspaceMode" eyebrow="Hardware pool" title="HWPOD 控制台" description="设备资源、主动出站 Node 与 Python 单文件接入的同一事实视图。">
      <template #actions>
        <nav class="console-tabs" aria-label="HWPOD 子页面">
          <button v-for="item in ([['devices', '设备资源'], ['nodes', 'HWPOD Node'], ['onboarding', '节点接入']] as const)" :key="item[0]" type="button" :data-active="tab === item[0]" :aria-current="tab === item[0] ? 'page' : undefined" @click="goTab(item[0])">
            {{ item[1] }}
          </button>
        </nav>
      </template>
      <template v-if="tab !== 'onboarding'" #filters>
        <form class="console-search" role="search" @submit.prevent="applySearch">
          <label for="hwpod-search">搜索</label>
          <input id="hwpod-search" v-model="searchText" type="search" placeholder="nodeId / hwpodId / 状态" />
          <button type="submit">查询</button>
        </form>
        <label class="status-filter">状态
          <select :value="viewState.filter.value" @change="setStatusFilter">
            <option value="">全部</option>
            <option v-for="option in statusFilterOptions" :key="option.value" :value="option.value">{{ option.label }}</option>
          </select>
        </label>
        <button class="probe-button" type="button" title="显式执行 workspace readiness 探测；普通页面读取不会 dispatch operation。" @click="probeCurrent">重新探测</button>
        <ViewModeSwitch :model-value="view" :options="[{ value: 'cards', label: tab === 'devices' ? '电路板卡片' : '卡片', icon: Grid2X2 }, { value: 'list', label: '列表', icon: List }]" label="显示模式" @update:model-value="setView($event as 'cards' | 'list')" />
        <ViewModeSwitch :model-value="density" :options="[{ value: 'comfortable', label: '舒适密度', icon: Rows4 }, { value: 'compact', label: '紧凑密度', icon: Rows3 }]" label="显示密度" @update:model-value="setDensity($event as 'compact' | 'comfortable')" />
      </template>
    </PageCommandBar>

    <BoundedWorkspace v-if="workspaceMode" class="hwpod-workspace is-operations-workspace" label="HWPOD 操作工作区">
      <HwpodOperationsWorkspace :hwpod-id="text(route.params.hwpodId)" :initial-path="text(route.query.path) || '.'" @back="closeWorkspace" @navigate="navigateWorkspace" />
    </BoundedWorkspace>

    <AsyncBoundary
      v-else
      :state="boundaryState"
      :has-content="hasContent"
      :error="hwpod.consoleError || 'HWPOD typed topology 当前不可用。'"
      empty-title="没有 HWPOD 资源"
      empty-description="owning registry 当前没有返回符合查询的 HWPOD 资源。"
      @retry="refreshCurrent"
    >
      <aside v-if="hwpod.consoleError && hwpod.consoleStale" class="stale-warning" role="status">
        <strong>显示最近成功快照</strong>
        <span>{{ hwpod.consoleError }}</span>
      </aside>
      <aside v-if="firstInvalidSpec" class="spec-warning" role="status">
        <strong>{{ invalidSpecs.length }} 份 invalid spec 未进入可用资源</strong>
        <span>{{ firstInvalidSpec.blocker.code }} · {{ firstInvalidSpec.blocker.summary }}</span>
      </aside>

      <BoundedWorkspace class="hwpod-workspace" label="HWPOD 资源工作区">
        <HwpodDevicesWorkspace v-if="tab === 'devices'" :devices="devices" :selected-id="selectedDevice?.hwpodId || null" :view="view" :density="density" :sort="viewState.sort.value" @select="selectDeviceItem" @sort="toggleSort" />
        <HwpodNodesWorkspace v-else-if="tab === 'nodes'" :nodes="nodes" :selected-id="selectedNodeDetail?.nodeId || null" :view="view" :density="density" :sort="viewState.sort.value" @select="selectNodeItem" @sort="toggleSort" />
        <HwpodOnboardingWorkspace v-else :metadata="hwpod.nodeUpdate" :nodes="nodes" :selected-node-id="selectedNode?.nodeId || ''" :stages="onboardingStages" :copied="copied" @copy="copySha" @node="chooseNodeId" />
      </BoundedWorkspace>
      <nav v-if="collectionPage && tab !== 'onboarding'" class="collection-pagination" aria-label="HWPOD 资源分页">
        <span>本页 {{ collectionPage.returned }} / 共 {{ collectionPage.total }}</span>
        <div>
          <button type="button" :disabled="!collectionPage.previousCursor" @click="setCursor(collectionPage.previousCursor)">上一页</button>
          <button type="button" :disabled="!collectionPage.nextCursor" @click="setCursor(collectionPage.nextCursor)">下一页</button>
        </div>
      </nav>
    </AsyncBoundary>

    <StatusStrip v-if="!workspaceMode" :items="statusItems" label="HWPOD typed topology 状态" live="polite" />

    <BaseDrawer :open="Boolean(selectedDevice) && !workspaceMode" :title="selectedDevice?.name || 'HWPOD 详情'" description="HWPOD API 返回的 typed DTO。" @close="closeInspector">
      <template v-if="selectedDevice">
        <dl class="resource-details">
          <div><dt>身份</dt><dd>{{ selectedDevice.hwpodId }}</dd></div>
          <div><dt>Node</dt><dd>{{ selectedDevice.nodeId }}</dd></div>
          <div><dt>Spec authority</dt><dd>{{ selectedDevice.source.authority }}</dd></div>
          <div><dt>最近 operation</dt><dd>{{ selectedDevice.operation?.status || '无' }}</dd></div>
          <div><dt>目标板</dt><dd>{{ selectedDevice.elements.target.label }}</dd></div>
          <div><dt>工作区</dt><dd>{{ selectedDevice.elements.workspace.label }}</dd></div>
          <div><dt>调试探头</dt><dd>{{ selectedDevice.elements.debugProbe.label }}</dd></div>
          <div><dt>IO 探头</dt><dd>{{ selectedDevice.elements.ioProbe.label }}</dd></div>
        </dl>
        <h3>缺失能力</h3>
        <div class="capability-list"><code v-for="capability in selectedDevice.missingCapabilities" :key="capability">{{ capability }}</code><span v-if="!selectedDevice.missingCapabilities.length">无</span></div>
        <h3>诊断摘要</h3>
        <p>{{ selectedDevice.diagnostic?.message || selectedDevice.blockers[0]?.summary || '无当前诊断' }}</p>
        <button class="workspace-command" type="button" @click="openWorkspace">打开工作区</button>
      </template>
    </BaseDrawer>

    <BaseDrawer :open="Boolean(selectedNodeDetail)" :title="selectedNodeDetail?.name || 'HWPOD Node 详情'" description="Node 连接、挂载 HWPOD、并发与 readiness 的 typed DTO。" @close="closeNodeInspector">
      <template v-if="selectedNodeDetail">
        <dl class="resource-details">
          <div><dt>Node ID</dt><dd>{{ selectedNodeDetail.nodeId }}</dd></div>
          <div><dt>运行时</dt><dd>{{ selectedNodeDetail.runtimeKind || '未识别' }} · {{ selectedNodeDetail.version || '未知版本' }}</dd></div>
          <div><dt>in-flight</dt><dd>{{ selectedNodeDetail.inFlight.count }}/{{ selectedNodeDetail.inFlight.max }}</dd></div>
          <div><dt>最近用户 operation</dt><dd>{{ selectedNodeDetail.operation?.status || '无' }}</dd></div>
          <div><dt>最近 readiness probe</dt><dd>{{ selectedNodeDetail.readinessProbe?.status || '未探测' }}</dd></div>
        </dl>
        <h3>挂载 HWPOD</h3>
        <div class="capability-list"><code v-for="device in selectedNodeDetail.hwpods" :key="device.hwpodId">{{ device.hwpodId }} · {{ statusLabel(device.status) }}</code><span v-if="!selectedNodeDetail.hwpods.length">无</span></div>
        <h3>接入 readiness</h3>
        <HwpodReadinessRail :stages="selectedNodeDetail.readiness" />
      </template>
    </BaseDrawer>
  </section>
</template>

<style scoped>
.hwpod-console { display: grid; height: 100%; min-width: 0; min-height: 0; grid-template-rows: 44px minmax(0, 1fr) 28px; align-content: stretch; gap: 6px; padding: 8px; color: var(--console-graphite-900); overflow: hidden; }
.hwpod-console[data-workspace-mode="true"] { grid-template-rows: minmax(0, 1fr); gap: 0; }
.hwpod-console :deep(.console-async-boundary) { display: flex; min-height: 0; flex-direction: column; overflow: hidden; }
.hwpod-console :deep(.page-command-bar) { min-height: 44px; grid-template-columns: auto minmax(0, 1fr) auto; gap: 8px; align-items: center; padding: 4px 6px 4px 12px; }
.hwpod-console :deep(.page-command-bar::before) { width: 3px; }
.hwpod-console :deep(.page-command-copy) { display: block; }
.hwpod-console :deep(.page-command-eyebrow), .hwpod-console :deep(.page-command-description) { display: none; }
.hwpod-console :deep(.page-command-title) { font-size: 16px; line-height: 1; white-space: nowrap; }
.hwpod-console :deep(.page-command-actions) { grid-column: 3; grid-row: 1; }
.hwpod-console :deep(.page-command-filters) { grid-column: 2; grid-row: 1; flex-wrap: nowrap; align-items: center; justify-content: flex-end; gap: 5px; padding-bottom: 0; overflow-x: auto; overscroll-behavior-inline: contain; }
.hwpod-console :deep(.console-status-strip) { height: 28px; min-height: 28px; border-radius: 0; box-shadow: none; }
.hwpod-console :deep(.console-status-item) { min-width: 120px; min-height: 26px; grid-template-columns: auto auto; grid-template-rows: 1fr; justify-content: start; gap: 6px; padding: 0 9px 0 14px; }
.hwpod-console :deep(.console-status-item::before) { top: auto; bottom: auto; left: 6px; width: 5px; height: 5px; border-radius: 50%; }
.hwpod-console :deep(.console-status-item dt), .hwpod-console :deep(.console-status-item dd) { font-size: 9px; }
.console-tabs { display: flex; gap: 2px; width: fit-content; max-width: 100%; padding: 2px; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-border); overflow-x: auto; }
.console-tabs button { min-height: 28px; border: 0; border-radius: 4px; background: var(--console-surface-raised); color: var(--console-graphite-600); padding: 0 10px; font-size: 11px; font-weight: 750; white-space: nowrap; cursor: pointer; }
.console-tabs button[data-active="true"] { color: #fff; background: var(--console-graphite-900); }
.console-search { display: flex; width: min(340px, 100%); min-width: min(340px, 100%); align-items: center; gap: 6px; }
.console-search label, .status-filter { color: var(--console-graphite-600); font-family: var(--console-font-mono); font-size: 10px; font-weight: 760; letter-spacing: 0; text-transform: uppercase; }
.console-search input { height: 28px; min-width: 0; flex: 1; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 0 8px; font-size: 10px; }
.console-search button { height: 28px; border: 1px solid var(--console-cyan-700); border-radius: var(--console-radius-sm); background: var(--console-cyan-100); color: var(--console-cyan-700); padding: 0 8px; font-size: 10px; font-weight: 750; cursor: pointer; }
.status-filter { display: flex; align-items: center; gap: 7px; }
.status-filter select { height: 28px; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 0 7px; color: var(--console-graphite-800); font-size: 10px; }
.probe-button { height: 28px; border: 1px solid var(--console-amber-500); border-radius: var(--console-radius-sm); background: var(--console-amber-100); color: var(--console-amber-700); padding: 0 8px; font-size: 10px; font-weight: 750; cursor: pointer; white-space: nowrap; }
.hwpod-console :deep(.view-mode-switch) { height: 28px; flex: 0 0 auto; }
.hwpod-console :deep(.view-mode-switch button) { height: 24px; min-height: 24px; padding-block: 0; font-size: 10px; }
.spec-warning { display: flex; gap: 8px 20px; align-items: center; margin-bottom: 10px; padding: 10px 13px; border: 1px solid #dfc47d; border-left: 4px solid #aa7a18; border-radius: 7px; background: #fff8df; color: #6b5317; font-size: .78rem; }
.stale-warning { display: flex; gap: 8px 20px; align-items: center; margin-bottom: 10px; padding: 10px 13px; border: 1px solid #e4a8a2; border-left: 4px solid var(--console-red-700); border-radius: var(--console-radius-sm); background: var(--console-red-100); color: var(--console-red-700); font-size: .78rem; }
.hwpod-workspace { min-height: 0; flex: 1 1 auto; }
.hwpod-workspace :deep(.bounded-workspace-main) { padding: 12px; }
.hwpod-workspace.is-operations-workspace :deep(.bounded-workspace-main) { overflow: hidden; padding: 0; }
.hwpod-workspace :deep(.resource-stage) { display: grid; height: 100%; min-width: 0; min-height: 0; grid-template-rows: minmax(0, 1fr); }
.hwpod-workspace :deep(.grid-stack) { display: grid; min-width: 0; gap: 3px; }
.hwpod-workspace :deep(.grid-stack strong), .hwpod-workspace :deep(.grid-stack code), .hwpod-workspace :deep(.grid-stack small) { min-width: 0; max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hwpod-workspace :deep(.grid-stack code), .hwpod-workspace :deep(.grid-stack small) { color: var(--console-graphite-600); font-size: 11px; }
.hwpod-workspace :deep(.status-chip) { display: inline-flex; padding: 4px 8px; border: 1px solid #aebeb9; border-radius: 999px; background: #eaf5f0; color: #2c6b5d; font-size: .7rem; font-weight: 800; white-space: nowrap; }
.hwpod-workspace :deep(.status-chip[data-status="busy"]) { color: #8b4c0a; background: #fff4df; border-color: #e6bd80; }
.hwpod-workspace :deep(.status-chip[data-status="offline"]), .hwpod-workspace :deep(.status-chip[data-status="workspace-blocked"]) { color: #7b332c; background: #fff0ed; border-color: #ddb2ac; }
.hwpod-workspace :deep(.status-chip[data-status="capability-mismatch"]), .hwpod-workspace :deep(.status-chip[data-status="mismatch"]) { color: #745d09; background: #fff9d9; border-color: #e4d26d; }
.hwpod-workspace :deep(.device-collection .resource-collection-items[data-view="cards"]) { grid-template-columns: repeat(auto-fill, minmax(min(440px, 100%), 680px)); justify-content: start; }
.hwpod-workspace :deep(.node-collection .resource-collection-items[data-view="cards"]) { grid-template-columns: repeat(auto-fill, minmax(min(380px, 100%), 680px)); justify-content: start; }
.resource-details { display: grid; gap: 1px; margin: 0 0 18px; border: 1px solid #d2d9d6; background: #d2d9d6; }
.resource-details div { padding: 10px 11px; background: #f6f6f1; }
dt { color: #6d7a75; font-size: .68rem; font-weight: 800; text-transform: uppercase; }
dd { margin: 3px 0 0; overflow-wrap: anywhere; }
.capability-list { display: flex; flex-wrap: wrap; gap: 6px; }
.capability-list code { padding: 5px 7px; border-radius: 5px; background: #fff3dd; color: #754813; }
.workspace-command { width: 100%; min-height: 36px; margin-top: 18px; border: 1px solid var(--console-cyan-700); border-radius: var(--console-radius-sm); background: var(--console-cyan-100); color: var(--console-cyan-700); font-weight: 800; cursor: pointer; }
.collection-pagination { display: flex; min-width: 0; align-items: center; justify-content: space-between; gap: 12px; color: var(--console-graphite-600); font-family: var(--console-font-mono); font-size: 11px; }
.collection-pagination div { display: flex; gap: 6px; }
.collection-pagination button { border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); color: var(--console-graphite-800); padding: 6px 10px; font-weight: 700; cursor: pointer; }
.collection-pagination button:disabled { opacity: .45; cursor: not-allowed; }
@media (max-width: 1280px) { .hwpod-console { grid-template-rows: auto minmax(0, 1fr) 28px; } .hwpod-console :deep(.page-command-bar) { grid-template-columns: minmax(0, 1fr) auto; } .hwpod-console :deep(.page-command-actions) { grid-column: 2; } .hwpod-console :deep(.page-command-filters) { grid-column: 1 / -1; grid-row: 2; justify-content: flex-start; } }
@media (max-width: 680px) { .hwpod-console { padding: 2px; } .console-tabs { width: 100%; } .console-tabs button { flex: 1 0 auto; } .console-search { width: 100%; min-width: 100%; } .status-filter { flex: 0 0 auto; } .spec-warning, .stale-warning { align-items: flex-start; flex-direction: column; } .hwpod-workspace :deep(.bounded-workspace-main) { padding: 8px; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; } }
</style>
