diff --git a/DESIGN.md b/DESIGN.md index edd988b..ed8dfa6 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -91,13 +91,13 @@ Recommended canvas colors: | `canvas-map` | `#eaf1f8` | Fallback map background | | `canvas-map-grid` | `rgba(37, 99, 235, 0.06)` | Subtle engineering grid | | `canvas-page` | `#f8fafc` | Non-map support pages | -| `canvas-panel` | `rgba(255, 255, 255, 0.76)` | Default floating panel | -| `canvas-panel-strong` | `rgba(255, 255, 255, 0.92)` | Dense text or form surface | -| `canvas-panel-soft` | `rgba(255, 255, 255, 0.58)` | Low-density spatial overlay | -| `surface-overlay` | `rgba(255, 255, 255, 0.72-0.86)` | Floating menu or compact popover shell | -| `surface-readable` | `rgba(255, 255, 255, 0.90-0.96)` | Readable rows, menu headers, and content blocks inside translucent shells | +| `canvas-panel` | `rgba(255, 255, 255, 0.94)` | Default information-bearing floating panel | +| `canvas-panel-strong` | `rgba(255, 255, 255, 0.98)` | Dense text or form surface | +| `canvas-panel-soft` | `rgba(255, 255, 255, 0.84)` | Low-density spatial overlay without critical text | +| `surface-overlay` | `rgba(255, 255, 255, 0.90-0.94)` | Floating menu or compact panel shell | +| `surface-readable` | `rgba(255, 255, 255, 0.94)` | Readable rows, menu headers, property panels and content blocks | | `surface-map-control` | `rgba(255, 255, 255, 0.92)` | Always-visible and persistent map instruments: toolbar, zoom, scaleline, Agent shell, scheduled-condition shell | -| `surface-tool-panel` | `rgba(255, 255, 255, 0.78)` | Toolbar function panel outer shell | +| `surface-tool-panel` | `rgba(255, 255, 255, 0.90)` | Toolbar function panel outer shell | ### Color System @@ -203,8 +203,8 @@ Elevation should communicate interaction depth over the map. |---|---|---| | `map` | No shadow | Basemap and business layers | | `control` | White 90-96%, soft border, small shadow | Zoom, basemap, tool buttons, persistent command shells | -| `panel` | White 58-82%, backdrop blur, blue-gray shadow | Toolbar function panels, temporary result panels, layer panels | -| `focus` | White 92-98%, stronger border and shadow | Popover, confirmation, detailed forms | +| `panel` | White 90-94%, backdrop blur, blue-gray shadow | Toolbar function panels, temporary result panels, layer panels | +| `focus` | White 94%, 24px blur, stronger edge and shadow | Popover, confirmation, detailed forms | | `modal` | Scrim plus focused surface | Destructive or irreversible decisions | Recommended shadows: @@ -217,14 +217,89 @@ Use `backdrop-filter: blur(20px)` only where the panel remains readable. If the #### Transparency Layering -Use consistent opacity tiers for floating UI over the map: +Transparency is a material system, not a collection of arbitrary opacity utilities. Components must use one of the named material tokens below instead of choosing independent values such as `bg-white/80`, `bg-white/90`, or `bg-white/95`. -- Outer shells such as dropdown menus, compact popovers, and temporary command surfaces should use `surface-overlay` around `rgba(255, 255, 255, 0.72-0.86)`. -- Persistent command instruments that remain visible while the operator works, such as the Agent shell and scheduled-condition shell, should use `surface-map-control` at `rgba(255, 255, 255, 0.92)` rather than a low-opacity panel shell. -- Inner readable content such as menu headers, selectable rows, status groups, and form blocks should use `surface-readable` around `rgba(255, 255, 255, 0.90-0.96)`. -- Dense text, destructive confirmations, and detailed forms may use `canvas-panel-strong` around `rgba(255, 255, 255, 0.92)` when legibility matters more than map transparency. -- Do not put long text directly on a low-opacity shell. Give it a local readable surface, or increase the shell opacity. -- Keep readable content at least as opaque as its surrounding shell. Labels, values, and commands should sit on the more readable layer, not directly on a lower-opacity shell. +#### Transparency Material Tokens + +| Token | Background | Blur | Shadow | Intended use | +|---|---|---:|---|---| +| `glass-air` | `rgba(255, 255, 255, 0.84)` | `16px` | `0 10px 30px rgba(15, 23, 42, 0.10)` | Sparse map labels, scale line and low-density overlays without critical text | +| `glass-navigation` | `rgba(255, 255, 255, 0.90)` | `24px` | `0 8px 28px rgba(15, 23, 42, 0.10)` | Top bar and other broad, low-density navigation surfaces | +| `glass-panel` | `rgba(255, 255, 255, 0.90)` | `20px` | `0 24px 70px rgba(30, 64, 115, 0.18)` | Layer, base-map, measure, drawing and other temporary tool panels | +| `glass-readable` | `rgba(255, 255, 255, 0.94)` | `20px` | `0 24px 70px rgba(30, 64, 115, 0.20)` | Feature side panels, SCADA details, menus, compact results and information surfaces | +| `glass-control` | `rgba(255, 255, 255, 0.92)` | `16px` | `0 14px 34px rgba(15, 23, 42, 0.18)` | Persistent map instruments, Agent shell and scheduled-condition shell | +| `glass-menu` | `rgba(255, 255, 255, 0.94)` | `20px` | `0 18px 48px rgba(15, 23, 42, 0.16)` | Dropdown menus, notices, command palettes and transient choices | +| `glass-focus` | `rgba(255, 255, 255, 0.94)` | `24px` | `0 30px 90px rgba(15, 23, 42, 0.26)` | Centered feature popovers, SCADA popovers and focused confirmations | +| `surface-solid` | `rgba(255, 255, 255, 0.98)` | `0` | `0 20px 60px rgba(15, 23, 42, 0.18)` | Forms, reports, long text, destructive confirmations and accessibility fallback | +| `scrim-soft` | `rgba(15, 23, 42, 0.20)` | `0` | none | Non-destructive drawers and contextual focus | +| `scrim-focus` | `rgba(15, 23, 42, 0.48)` | `0` | none | Modal decisions, destructive or irreversible actions | + +Supporting edge and highlight tokens: + +| Token | Value | Use | +|---|---:|---| +| `glass-edge` | `rgba(255, 255, 255, 0.58)` | Outer glass edge, preferably an inset ring or layered shadow rather than a solid border | +| `glass-edge-strong` | `rgba(255, 255, 255, 0.76)` | Top-facing edge on menus and focused popovers | +| `glass-divider` | `rgba(100, 116, 139, 0.16)` | Internal section and row dividers | +| `glass-highlight` | `rgba(255, 255, 255, 0.46)` | Subtle hover highlight on a translucent surface | +| `glass-shadow-color` | `rgba(30, 64, 115, 0.18)` | Map-facing panel shadow tint | + +#### Colored Glass Tokens + +Colored glass communicates operational meaning. It must not be used as decoration. + +| Token | Background | Edge | Use | +|---|---:|---:|---| +| `glass-info` | `rgba(239, 246, 255, 0.78)` | `rgba(147, 197, 253, 0.42)` | Selection, informational state, radar-level devices | +| `glass-normal` | `rgba(240, 253, 250, 0.78)` | `rgba(94, 234, 212, 0.40)` | Healthy state, water-quality devices | +| `glass-warning` | `rgba(255, 247, 237, 0.82)` | `rgba(253, 186, 116, 0.46)` | Warning, ultrasonic-flow devices and attention state | +| `glass-danger` | `rgba(254, 242, 242, 0.86)` | `rgba(252, 165, 165, 0.48)` | Incident, validation failure and destructive warning | +| `glass-agent` | `rgba(245, 243, 255, 0.82)` | `rgba(196, 181, 253, 0.42)` | Agent inference only | + +Text on colored glass must use a darker shade of the same hue. Do not place neutral gray text on a colored surface. + +#### Component Material Mapping + +| Component category | Material token | Notes | +|---|---|---| +| Workbench top bar | `glass-navigation` | Preserve map context across the full width; active items use a semantic colored glass token | +| Zoom, scale line and compact toolbar | `glass-control` | Persistent controls need stable definition over every basemap and use `glass-edge` rather than an opaque border | +| Layer, base-map, measure and drawing panels | `glass-panel` | Use one translucent outer shell; repeated controls inside should be flat or lightly tinted | +| Agent command shell | `glass-control` | Conversation content may use `surface-solid` only for long generated text or form controls | +| Scheduled-condition task shell | `glass-control` | Remove stacked white layers; use dividers and state tint to create hierarchy | +| Feature and SCADA property popovers | `glass-focus` | Reuse the proven high-readability shell and distinguish focus through blur and elevation rather than a whiter background | +| Feature insight side panel | `glass-readable` | Dense values remain on the shared glass surface; avoid card-per-field nesting | +| Dropdown menus and notices | `glass-menu` | More opaque because the surface is temporary and action-dense | +| Reports, detailed forms and text-heavy results | `surface-solid` | Do not use glass for long reading or data entry | +| Status pills and device-type labels | semantic colored glass | Use `glass-normal`, `glass-info`, `glass-warning`, `glass-danger`, or `glass-agent` | +| Dialog backdrop | `scrim-soft` or `scrim-focus` | Choose by consequence, not by dialog size | + +#### Composition Rules + +- Use at most two translucent layers over the map at the same location. Three stacked white-alpha surfaces become visually opaque and waste GPU work. +- A glass shell should usually contain flat sections separated by `glass-divider`. Do not place an opaque white card around every row. +- Long text, editable fields, tables with many columns and destructive confirmations use `surface-solid` even when their parent shell is translucent. +- Readable content must never be less opaque than its surrounding shell. +- When map detail beneath the panel is too noisy, move one step toward `surface-solid`. Do not keep increasing blur. +- Use colored glass only for status, selection, device type or Agent inference. Neutral structure remains white glass. +- Semantic colored glass must sit inside a `glass-readable`, `glass-control`, `glass-menu`, or `glass-focus` neutral base. Do not place a 70-82% colored surface directly over the map. +- High-readability glass is the global default for information-bearing UI. Any surface containing operational text must be at least 90% opaque; properties, results, menus and multi-line content use 94%. +- Acrylic-style saturation and edge highlights are limited to broad navigation and transient outer shells. Do not add texture, tint or refractive treatment to property tables, forms, reports or long text. +- Prefer layered `box-shadow` or an inset ring for glass edges. A solid high-contrast border breaks the depth illusion. +- Hover feedback may change highlight and edge opacity. Do not animate `backdrop-filter` or background blur. + +#### CSS Token Contract + +`app/globals.css` is the single source of truth for material token values. Component code should consume semantic variables or shared utility classes rather than reproducing raw RGBA values. This guide documents intent and mapping only, so token values do not drift between CSS and documentation. + +#### Performance And Accessibility Fallback + +- Under `prefers-reduced-transparency: reduce`, replace every glass material with `surface-solid` and remove `backdrop-filter`. +- When `backdrop-filter` is unsupported, use `rgba(255, 255, 255, 0.96)` with the same shadow and radius. +- On low-power or touch-first devices, prefer `glass-readable` or `surface-solid` over a larger blur radius. +- On coarse-pointer devices, remove backdrop blur and use a 0.96-0.98 white surface while preserving semantic state colors. +- Never apply backdrop blur separately to every list row. Blur belongs to the shared outer surface. +- Maintain WCAG AA contrast for all body text and controls against the composited result, not only against the declared RGBA value. ### Motion @@ -481,7 +556,7 @@ Layer visibility and basemap selection belong together in the Layers tool panel: - Business layer visibility appears first. - Basemap selection appears below business layers. -- Toolbar function panels use `surface-tool-panel` (`rgba(255, 255, 255, 0.78)`) for the outer shell. +- Toolbar function panels use `surface-tool-panel` (`rgba(255, 255, 255, 0.90)`) for the outer shell. - Internal list rows, panel headers, swatches, result blocks, and action rows use `surface-readable`, around `0.94-0.96`, for readability. - MVT sources should keep `bounds` where available to avoid invalid tile requests. This must not be confused with map view extent; the map view itself should remain unrestricted unless a workflow explicitly requires a view constraint. diff --git a/app/globals.css b/app/globals.css index b7d8162..e8558e1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -7,8 +7,8 @@ background: #eaf1f8; --canvas-map: #eaf1f8; --canvas-map-grid: rgba(37, 99, 235, 0.06); - --canvas-panel: rgba(255, 255, 255, 0.76); - --canvas-panel-strong: rgba(255, 255, 255, 0.92); + --canvas-panel: rgba(255, 255, 255, 0.94); + --canvas-panel-strong: rgba(255, 255, 255, 0.98); --action-blue: #2563eb; --action-blue-hover: #1d4ed8; --action-blue-soft: #dbeafe; @@ -20,6 +20,30 @@ --shadow-control: 0 14px 34px rgba(15, 23, 42, 0.18); --shadow-panel: 0 24px 70px rgba(30, 64, 115, 0.2); --shadow-focus: 0 30px 90px rgba(15, 23, 42, 0.26); + --glass-air: rgba(255, 255, 255, 0.84); + --glass-navigation: rgba(255, 255, 255, 0.9); + --glass-panel: rgba(255, 255, 255, 0.9); + --glass-readable: rgba(255, 255, 255, 0.94); + --glass-control: rgba(255, 255, 255, 0.92); + --glass-menu: rgba(255, 255, 255, 0.94); + --glass-focus: rgba(255, 255, 255, 0.94); + --surface-solid: rgba(255, 255, 255, 0.98); + --glass-edge: rgba(255, 255, 255, 0.58); + --glass-edge-strong: rgba(255, 255, 255, 0.76); + --glass-divider: rgba(100, 116, 139, 0.16); + --glass-highlight: rgba(255, 255, 255, 0.46); + --glass-info: rgba(239, 246, 255, 0.78); + --glass-normal: rgba(240, 253, 250, 0.78); + --glass-warning: rgba(255, 247, 237, 0.82); + --glass-danger: rgba(254, 242, 242, 0.86); + --glass-agent: rgba(245, 243, 255, 0.82); + --scrim-soft: rgba(15, 23, 42, 0.2); + --scrim-focus: rgba(15, 23, 42, 0.48); + --blur-air: 16px; + --blur-navigation: 24px; + --blur-control: 16px; + --blur-panel: 20px; + --blur-focus: 24px; --background: 210 40% 98%; --foreground: 222 47% 11%; --card: 0 0% 100%; @@ -84,14 +108,196 @@ input:focus-visible { } .command-panel { - border: 1px solid var(--border-soft); - background: var(--canvas-panel); - box-shadow: var(--shadow-panel); - backdrop-filter: blur(20px); + background: var(--glass-panel); + box-shadow: + inset 0 1px 0 var(--glass-edge), + var(--shadow-panel); + backdrop-filter: blur(var(--blur-panel)) saturate(1.08); + -webkit-backdrop-filter: blur(var(--blur-panel)) saturate(1.08); +} + +.glass-navigation, +.glass-panel, +.glass-readable, +.glass-control, +.glass-menu, +.glass-focus, +.glass-info, +.glass-normal, +.glass-warning, +.glass-danger, +.glass-agent { + border-color: transparent; + background-clip: padding-box; +} + +.glass-navigation { + background-color: var(--glass-navigation); + box-shadow: + inset 0 1px 0 var(--glass-edge), + 0 8px 28px rgba(15, 23, 42, 0.1); + backdrop-filter: blur(var(--blur-navigation)) saturate(1.08); + -webkit-backdrop-filter: blur(var(--blur-navigation)) saturate(1.08); +} + +.glass-panel { + background-color: var(--glass-panel); + box-shadow: + inset 0 1px 0 var(--glass-edge), + 0 24px 70px rgba(30, 64, 115, 0.18); + backdrop-filter: blur(var(--blur-panel)) saturate(1.08); + -webkit-backdrop-filter: blur(var(--blur-panel)) saturate(1.08); +} + +.glass-readable { + background-color: var(--glass-readable); + box-shadow: + inset 0 1px 0 var(--glass-edge-strong), + 0 24px 70px rgba(30, 64, 115, 0.2); + backdrop-filter: blur(var(--blur-panel)) saturate(1.06); + -webkit-backdrop-filter: blur(var(--blur-panel)) saturate(1.06); +} + +.glass-control { + background-color: var(--glass-control); + box-shadow: + inset 0 1px 0 var(--glass-edge-strong), + var(--shadow-control); + backdrop-filter: blur(var(--blur-control)) saturate(1.08); + -webkit-backdrop-filter: blur(var(--blur-control)) saturate(1.08); +} + +.glass-menu { + background-color: var(--glass-menu); + box-shadow: + inset 0 1px 0 var(--glass-edge-strong), + 0 18px 48px rgba(15, 23, 42, 0.16); + backdrop-filter: blur(var(--blur-panel)) saturate(1.04); + -webkit-backdrop-filter: blur(var(--blur-panel)) saturate(1.04); +} + +.glass-focus { + background-color: var(--glass-focus); + box-shadow: + inset 0 1px 0 var(--glass-edge-strong), + var(--shadow-focus); + backdrop-filter: blur(var(--blur-focus)) saturate(1.06); + -webkit-backdrop-filter: blur(var(--blur-focus)) saturate(1.06); +} + +.glass-info { + background-color: var(--glass-info); + box-shadow: inset 0 1px 0 rgba(147, 197, 253, 0.42); +} + +.glass-normal { + background-color: var(--glass-normal); + box-shadow: inset 0 1px 0 rgba(94, 234, 212, 0.4); +} + +.glass-warning { + background-color: var(--glass-warning); + box-shadow: inset 0 1px 0 rgba(253, 186, 116, 0.46); +} + +.glass-danger { + background-color: var(--glass-danger); + box-shadow: inset 0 1px 0 rgba(252, 165, 165, 0.48); +} + +.glass-agent { + background-color: var(--glass-agent); + box-shadow: inset 0 1px 0 rgba(196, 181, 253, 0.42); +} + +@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { + .glass-navigation, + .glass-panel, + .glass-readable, + .glass-control, + .glass-menu, + .glass-focus, + .command-panel { + background-color: rgba(255, 255, 255, 0.96); + } + + .glass-info { + background-color: #eff6ff; + } + + .glass-normal { + background-color: #f0fdfa; + } + + .glass-warning { + background-color: #fff7ed; + } + + .glass-danger { + background-color: #fef2f2; + } + + .glass-agent { + background-color: #f5f3ff; + } +} + +@media (prefers-reduced-transparency: reduce) { + .glass-navigation, + .glass-panel, + .glass-readable, + .glass-control, + .glass-menu, + .glass-focus, + .command-panel { + background-color: var(--surface-solid); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .glass-info { + background-color: #eff6ff; + } + + .glass-normal { + background-color: #f0fdfa; + } + + .glass-warning { + background-color: #fff7ed; + } + + .glass-danger { + background-color: #fef2f2; + } + + .glass-agent { + background-color: #f5f3ff; + } +} + +@media (pointer: coarse) { + .glass-navigation, + .glass-panel, + .glass-readable, + .glass-control, + .glass-menu, + .glass-focus, + .command-panel { + background-color: rgba(255, 255, 255, 0.96); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + + .glass-control, + .glass-menu, + .glass-focus { + background-color: var(--surface-solid); + } } .agent-panel-band { - background: rgba(255, 255, 255, 0.96); + background: var(--glass-menu); } .agent-panel-conversation { diff --git a/features/agent/components/agent-command-panel.tsx b/features/agent/components/agent-command-panel.tsx index b712508..fe1a811 100644 --- a/features/agent/components/agent-command-panel.tsx +++ b/features/agent/components/agent-command-panel.tsx @@ -52,7 +52,7 @@ import { agentPanelSectionVariants } from "./agent-motion"; import { AgentHistoryPanel } from "./agent-history-panel"; -import { AgentMessageDetails } from "./agent-message-details"; +import { AgentMessageDetails, AgentMessageProgress } from "./agent-message-details"; import { AgentOperationalBrief } from "./agent-operational-brief"; import { AgentUiEnvelopeRenderer } from "./agent-ui-envelope-renderer"; import { StreamingTokenResponse } from "./streaming-token-response"; @@ -175,7 +175,7 @@ export function AgentCommandPanel({
对话主题
- + {getAgentConnectionLabel(statusLabel)} @@ -313,15 +313,17 @@ export function AgentCommandPanel({@@ -438,7 +479,7 @@ function QuestionRequestCard({ }; return ( -