From 16ecb69d005966af527788d3f763792c4c703847 Mon Sep 17 00:00:00 2001 From: Huarch Date: Wed, 15 Jul 2026 17:07:32 +0800 Subject: [PATCH] style: refine acrylic workbench surfaces --- .gitignore | 2 + DESIGN.md | 237 ++++------- app/globals.css | 390 ++++++++++-------- .../agent/components/agent-collapsed-rail.tsx | 10 +- .../agent/components/agent-command-panel.tsx | 57 +-- .../components/agent-message-details.tsx | 8 +- .../components/agent-operational-brief.tsx | 4 +- .../components/agent-ui-envelope-renderer.tsx | 2 +- .../core/components/base-layers-control.tsx | 2 +- .../map/core/components/control-panel.tsx | 4 +- .../map/core/components/layer-control.tsx | 4 +- features/map/core/components/layer-panel.tsx | 2 +- .../map/core/components/map-control-styles.ts | 14 +- .../map/core/components/measure-panel.tsx | 4 +- features/map/core/components/notice.tsx | 10 +- features/map/core/components/scale-line.tsx | 4 +- .../components/agent-task-ticker.tsx | 22 +- .../components/feature-insight-panel.tsx | 20 +- .../workbench/components/feature-popover.tsx | 17 +- features/workbench/components/icon-button.tsx | 2 +- .../scheduled-condition-detail-panel.tsx | 48 +-- .../components/scheduled-condition-feed.tsx | 61 +-- .../components/workbench-top-bar-menus.tsx | 10 +- .../components/workbench-top-bar.tsx | 2 +- features/workbench/hooks/use-workbench-map.ts | 30 +- .../workbench/layout/workbench-layout.test.ts | 13 + features/workbench/layout/workbench-layout.ts | 81 ++++ features/workbench/map-workbench-page.tsx | 61 ++- features/workbench/map/camera.test.ts | 61 ++- features/workbench/map/camera.ts | 37 +- .../workbench/map/feature-adapter.test.ts | 22 + features/workbench/map/feature-adapter.ts | 6 +- features/workbench/map/scada.test.ts | 16 +- features/workbench/map/scada.ts | 7 +- features/workbench/map/sources.test.ts | 34 ++ features/workbench/map/sources.ts | 24 +- .../utils/feature-properties.test.ts | 28 ++ .../workbench/utils/feature-properties.ts | 7 +- package.json | 3 +- playwright.config.ts | 17 + .../scheduled-condition-acrylic.e2e.ts | 61 +++ 41 files changed, 838 insertions(+), 606 deletions(-) create mode 100644 features/workbench/layout/workbench-layout.test.ts create mode 100644 features/workbench/layout/workbench-layout.ts create mode 100644 features/workbench/map/sources.test.ts create mode 100644 playwright.config.ts create mode 100644 tests/browser/scheduled-condition-acrylic.e2e.ts diff --git a/.gitignore b/.gitignore index f4a8916..290481f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .env.local .next/ node_modules/ +playwright-report/ +test-results/ tsconfig.tsbuildinfo 供水管网_Agent_WebGIS_设计与技术框架.md 现代 WebGIS 与 LLM Agent 地图 UI、UX 设计研究.md diff --git a/DESIGN.md b/DESIGN.md index d6f58cc..69b97ba 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -11,7 +11,7 @@ The visual direction is restrained technical immersion: - Full-screen spatial workspace by default. - Clear operational hierarchy over decorative spectacle. - Light engineering map as the default canvas. -- Floating translucent command surfaces with strong readability. +- Acrylic command instruments with strong reading surfaces. - Blue as the primary action and selection color. - Green/cyan for normal network flow, orange for risk, red for incident and impact, purple only for model or Agent inference. - Motion used to explain spatial change, simulation progress, and recommended actions. @@ -88,16 +88,16 @@ Recommended canvas colors: | Token | Value | Use | |---|---:|---| -| `canvas-map` | `#eaf1f8` | Fallback map background | +| `canvas-map` | `oklch(94.5% 0.012 250)` | Fallback map background | | `canvas-map-grid` | `rgba(37, 99, 235, 0.06)` | Subtle engineering grid | | `canvas-page` | `#f8fafc` | Non-map support pages | -| `canvas-panel` | Named glass material | Use the material level for the component category | -| `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` | `glass-menu` | Floating menu or compact choice shell | -| `surface-readable` | `glass-readable` | Right-side details, compact results and summaries | -| `surface-map-control` | `glass-control` | Always-visible toolbar, zoom and compact task shells | -| `surface-tool-panel` | `glass-panel` | Toolbar panels, expanded conditions and Agent shell | +| `acrylic-navigation` | Adaptive 68-82% cool-blue tint | Workbench top bar | +| `acrylic-panel` | Adaptive translucent cool-blue tint over active basemaps | Agent, conditions, menus and focused panels | +| `acrylic-control` | Adaptive clean cool-white tint with light blue edge | Toolbar, zoom, scaleline and standalone compact controls | +| `surface-control` | 95-98% cool-white, no blur | Headers, inputs and compact controls inside acrylic shells | +| `surface-well` | 28-34% cool-gray overlay, no blur | Conversation wells and empty support regions | +| `surface-reading` | 97-99% white, no blur | Dense text, forms, tables and messages | +| `glass-transient` | Adaptive 56-72% cool-blue acrylic | Active task ticker only | ### Color System @@ -112,8 +112,8 @@ Recommended canvas colors: | `ink-secondary` | `#334155` | Secondary labels and values | | `ink-muted` | `#64748b` | Descriptions, metadata | | `ink-disabled` | `#94a3b8` | Disabled controls | -| `border-soft` | `rgba(255, 255, 255, 0.62)` | Glass panel border | -| `border-strong` | `rgba(148, 163, 184, 0.34)` | Dense control border | +| `border-soft` | `oklch(90.5% 0.009 250)` | Dividers and nested groups | +| `border-strong` | `oklch(86.5% 0.012 250)` | Floating surface border | #### Operational Colors @@ -138,11 +138,12 @@ Color must carry meaning. Do not use orange, red, green, or purple as decoration Use a system font stack that performs well for Chinese and English operational UI: ```css -font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; +font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", + "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; ``` Avoid negative letter spacing. This product should optimize for scanning dense operational data, not editorial display styling. +Use tabular numbers for time, counters, coordinates and changing measurements. | Token | Size | Weight | Line Height | Use | |---|---:|---:|---:|---| @@ -197,131 +198,59 @@ Use a clear radius hierarchy over the map: ### Elevation -Elevation should communicate interaction depth over the map. +Elevation communicates operational hierarchy through adaptive acrylic outer shells, high-opacity reading surfaces and restrained shadows. The map may influence empty shell regions, but roads and satellite texture must not remain legible beneath operational text. -| Level | Treatment | Use | -|---|---|---| -| `map` | No shadow | Basemap and business layers | -| `control` | White 90-96%, soft border, small shadow | Zoom, basemap, tool buttons, persistent command shells | -| `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 | +#### Surface Tokens -Recommended shadows: +Tailwind owns layout and state. Global semantic classes own surface color, border and elevation. Do not reproduce their values inside components. -- `control`: `0 14px 34px rgba(15, 23, 42, 0.18)` -- `panel`: `0 24px 70px rgba(30, 64, 115, 0.20)` -- `focus`: `0 30px 90px rgba(15, 23, 42, 0.26)` - -Use `backdrop-filter: blur(20px)` only where the panel remains readable. If the map beneath is visually busy, increase surface opacity instead of increasing blur. - -#### Transparency Layering - -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`. - -#### Transparency Material Tokens - -| Token | Background | Blur | Shadow | Intended use | -|---|---|---:|---|---| -| `glass-air` | `rgba(255, 255, 255, 0.68)` | `16px` | `0 10px 30px rgba(15, 23, 42, 0.10)` | Scale line, coordinates and lightweight map hints | -| `glass-navigation` | `rgba(255, 255, 255, 0.80)` | `24px` | `0 8px 28px rgba(15, 23, 42, 0.10)` | Top bar and broad low-density navigation surfaces | -| `glass-panel` | `rgba(255, 255, 255, 0.84)` | `20px` | `0 24px 70px rgba(30, 64, 115, 0.18)` | Tool panels, expanded conditions and Agent shell | -| `glass-control` | `rgba(255, 255, 255, 0.90)` | `16px` | `0 14px 34px rgba(15, 23, 42, 0.18)` | Zoom, persistent toolbars and compact task shells | -| `glass-readable` | `rgba(255, 255, 255, 0.90)` | `20px` | `0 24px 70px rgba(30, 64, 115, 0.20)` | Right-side details, compact results and summaries | -| `glass-focus` | `rgba(255, 255, 255, 0.94)` | `24px` | Tailwind `shadow-2xl shadow-blue-950/20` | Normal and SCADA property popovers only | -| `glass-menu` | `rgba(255, 255, 255, 0.92)` | `20px` | `0 18px 48px rgba(15, 23, 42, 0.16)` | Dropdown menus and notices | -| `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 | +| Class | Background | Intended 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 | +| `acrylic-navigation` | 68% on light, 82% on satellite | Workbench top bar, blur 20-24px | +| `acrylic-panel` | Translucent cool-blue tint on light and satellite | Agent, conditions, menus, notices and focused popovers, blur 20-24px | +| `acrylic-control` | 78% on light, 90% on satellite | Toolbar, zoom, scaleline and standalone compact controls, blur 16-20px | +| `surface-control` | 95-98%, no blur | Headers, inputs and controls inside acrylic shells | +| `surface-well` | 34% on light, 28% on satellite | Conversation wells and low-density empty regions | +| `surface-reading` | 97-99%, no blur | Messages, reports, forms, tables and properties | +| `glass-transient` | 56% on light, 72% on satellite | Active task ticker, blur 16-20px | -#### Colored Glass Tokens +Semantic classes are `material-tone-info`, `material-tone-normal`, `material-tone-warning`, `material-tone-danger`, and `material-tone-agent`. They communicate state only, never hierarchy, and never apply blur. -Colored glass communicates operational meaning. It must not be used as decoration. +#### Component Mapping -| 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 | +| Component category | Surface token | +|---|---| +| Workbench top bar | `acrylic-navigation` | +| Agent command shell and collapsed rail | `acrylic-panel` | +| Conversation background | `surface-well` | +| Zoom, toolbar and scaleline | `acrylic-control` | +| Inputs and internal headers | `surface-control` or `surface-reading` | +| Tool panels, conditions, menus, notices and popovers | `acrylic-panel` | +| Messages, reports, tables and property blocks | `surface-reading` | +| Active task ticker | `glass-transient` | +| Ticker stack cards | `surface-control` | -Text on colored glass must use a darker shade of the same hue. Do not place neutral gray text on a colored surface. +The task ticker is always centered against the full viewport, independent of Agent or scheduled-condition panel widths. Hide it while the scheduled-condition detail is expanded instead of shifting it away from center. -#### Canonical Focus Glass +#### Composition And Performance -Normal and SCADA property popovers are the readability benchmark. Their outer shell must use this exact Tailwind combination: - -```text -bg-white/[0.94] backdrop-blur-xl -shadow-2xl shadow-blue-950/20 -ring-1 ring-white/80 -``` - -Do not add `saturate()`, gradients, acrylic effects, heavier custom focus shadows, or full-width white surfaces beneath the header and body. Attribute rows use low-opacity dividers. Device-type headers use only a small `bg-*-50/70` tint. - -#### Layering And Performance - -- Use at most one `backdrop-filter` in the same visual region. Inner cards never blur again. -- Do not place full-height `white/90+` child panels inside a glass shell. -- Large surfaces gain hierarchy by lowering white coverage, not by increasing blur. -- Reports, forms, long text, multi-column tables and dangerous confirmations use `surface-solid`. -- Browsers without backdrop-filter fall back to 96% white. -- `prefers-reduced-transparency` disables blur and uses `surface-solid`. -- Coarse pointers retain transparency. Use a solid surface only behind an explicit performance-degradation condition. - -#### 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 | -| Scale line, coordinates and lightweight hints | `glass-air` | Sparse overlays should preserve the strongest perception of the scattered map | -| Zoom 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-panel` | Conversation content may use `surface-solid` only for long generated text or form controls | -| Scheduled-condition task shell | `glass-control` collapsed, `glass-panel` expanded | Remove stacked full-height 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 one backdrop-filter layer over the map at the same location. Inner translucent tints must not apply blur. -- 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. -- When map detail beneath the panel is too noisy, move the outer shell one material level toward `surface-solid`. Do not keep increasing blur or add an opaque full-height child. -- 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. -- Opacity follows component area and information density. Operational text may live on an 84% panel shell when local long-form content uses `surface-solid`; only property focus popovers use 94% glass. -- Do not apply acrylic-style saturation, texture or refractive treatment to any material level. -- 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. +- Apply `backdrop-filter` only to the outermost acrylic shell. Descendants must use non-filtered surface classes. +- Keep the acrylic shell as the first composited visual layer above the map. Positioning ancestors must not retain `transform`, `filter`, `opacity`, `isolation` or `will-change: transform`; presence motion belongs on the acrylic shell itself and must leave no persistent compositor hint after it settles. +- Scheduled conditions use `acrylic-panel` only for the outer shell. Internal headers, filters, timeline rows, detail reports and KPI blocks use non-filtered content surfaces (`surface-control` or `surface-reading`) so the panel has one acrylic layer and readable upper content. +- The acrylic recipe is blur plus controlled backdrop saturation, a cool tint, a luminosity layer, a low-opacity exclusion layer and fine monochrome noise. Blur and tint alone are only frosted glass and do not satisfy this contract. +- Follow Microsoft Fluent acrylic principles: tint, blur, noise and layer purpose create the material. Do not use gray or muddy color casts to make glass feel deeper. +- Acrylic regions may coexist only when they do not overlap. Tool panels replace the condition panel instead of stacking over it. +- Floating acrylic panels use a translucent edge, an inset top highlight and one restrained shadow. +- Light, satellite and no-basemap modes adjust acrylic opacity through `data-basemap-tone`; they do not alter component markup. +- Separate adjacent regions with dividers or background steps instead of nested cards. +- Use semantic color only for status, selection, device type or Agent inference. +- Under `prefers-reduced-transparency: reduce`, all acrylic and glass surfaces become 98% opaque and remove blur. +- Browsers without `backdrop-filter` support use the same high-opacity fallback. +- Maintain WCAG AA contrast against the declared reading surface. #### 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. -- Low-power fallback must be activated by an explicit performance-degradation condition, not inferred from touch or coarse pointer input. -- Coarse-pointer devices keep the same transparency hierarchy by default. -- 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. +`app/globals.css` is the single source of truth for surface values. `features/workbench/layout/workbench-layout.ts` is the single source for dock, condition, toolbar and ticker geometry used by both the rendered shell and map camera padding. ### Motion @@ -371,7 +300,7 @@ The workspace should be composed from five persistent regions: | Region | Role | Behavior | |---|---|---| | Map Canvas | Primary spatial context | Always visible, full viewport | -| Command Top Bar | System state | Thin, translucent, status-oriented | +| Command Top Bar | System state | Thin, acrylic, status-oriented | | Agent Command Panel | Reasoning and recommended actions | Left side, collapsible, task-focused | | Analysis / Layer Rail | Tools, layers, results | Right side, icon-first, panels open on demand | | Simulation Timeline | Scenario time and playback | Bottom dock, visible during simulation | @@ -415,23 +344,23 @@ Use this pattern for persistent map instruments that start as compact lists and The Agent panel is the main decision companion. It should make the Agent auditable and controllable. -On desktop, the Agent panel may be wider than a utility side panel because it carries the auditable decision chain. Use a preferred width around `460px`, with room to expand up to about `520px` on wide command workstations when the map camera reserves matching left padding. Do not widen it on medium or small screens; there it should become narrower or overlay on demand. +On desktop, the Agent panel is a floating left-side instrument with 12px left, 96px top and 16px bottom offsets. Use `460px` from `1280px` through `1535px` and `500px` at `1536px` and above. Below `1024px` it becomes the existing single primary mobile panel. The Agent panel is a persistent command instrument, so its outer shell should use the same control surface family as the map toolbar, zoom control, and scheduled-condition panel. It is not a low-opacity temporary glass panel. -| Agent surface | Opacity | Use | -|---|---:|---| -| Outer shell | `rgba(255, 255, 255, 0.92)` | Main floating panel, collapsed rail, and mobile entry. Reuse the map control surface treatment: soft white border, compact control shadow, subtle dark ring, and backdrop blur. | -| Header, context strip, action tray, input band | `rgba(255, 255, 255, 0.96)` | Persistent readable bands that organize the command surface. | -| Conversation well | `rgba(255, 255, 255, 0.34)` | Low-density scroll area behind messages. Do not place text directly on this layer. | -| Message and recommendation surfaces | `rgba(255, 255, 255, 0.94)` | Assistant messages, recommendation groups, and primary readable content. | -| Nested evidence/tool result blocks | `rgba(248, 250, 252, 0.90)` | Evidence tiles, tool run rows, and compact structured outputs inside messages. | -| Prompt input and secondary controls | `rgba(255, 255, 255, 0.96)` | Text entry, secondary buttons, and controls that need crisp hit targets. | -| Active confirmation surface | `rgba(255, 247, 237, 0.96)` | Agent action review and confirmation state. | +| Agent region | Surface | Use | +|---|---|---| +| Outer floating shell | `acrylic-panel` | Persistent command region with adaptive opacity and one outer blur | +| Header and action bands | `surface-control` | Quiet organizing bands | +| Conversation well | `surface-well` | Low-density scroll area behind messages | +| Message and recommendation surfaces | `surface-reading` | Assistant messages and primary readable content | +| Nested evidence/tool result blocks | `surface-control` or `surface-reading` | Choose by text density | +| Prompt input and secondary controls | `surface-reading` | Stable text-entry contrast | +| Active confirmation surface | warning tone | Agent action review and confirmation state | -Do not return the Agent shell to a separate `0.78` panel treatment while the toolbar, zoom, and scheduled-condition panel use `surface-map-control`; those surfaces should read as one instrument family. Keep the conversation well lighter than the shell, and use readable local message surfaces for operational text. Do not use `0.58` or lower for any Agent region that contains operational text. +Keep the conversation well visibly darker than the reading surfaces. Operational text must never sit directly on the map or on a translucent layer. -Collapsed Agent state should be content-sized, not full height. It should keep only the expand button, Agent identity, and pending status indicators. Expanding and collapsing should use a short `150-180ms` reveal: opacity, `4-8px` horizontal translation, and very small scale change. Do not animate the map camera when the Agent panel changes state. +Collapsed Agent state is a floating 136px compact instrument that keeps the Agent identity and expand action. Expanding and collapsing uses a short `150-180ms` reveal with opacity and a small horizontal translation. The map updates padding without animating business layers. Required sections: @@ -458,17 +387,18 @@ The Agent should use purple only for model identity or inference metadata. Prima The scheduled-condition panel is a persistent right-side command instrument for live operational tasks. It should visually belong to the same family as the toolbar, zoom control, and Agent shell, not to temporary tool panels. -Use the same shell treatment as `surface-map-control`: +Use the adaptive acrylic outer-shell contract while preserving the established collapsible-detail geometry: -- Outer shell: `rgba(255, 255, 255, 0.92)`, soft white border, subtle dark ring, compact control shadow, and `backdrop-blur-xl`. +- Outer shell: `acrylic-panel` in both states. - Outer radius: `rounded-2xl`, because the component reads as a major floating panel even when collapsed. -- Header, list containers, rows, and detail cards: `surface-readable` or `canvas-panel-strong`, around `0.94-0.96`. +- Header and compact controls: `surface-control`. Detail and report content: `surface-reading`. - Icon cells: `rounded-lg`, matching toolbar icon cells. - Rows and compact controls: `rounded-xl`. - Detail content blocks: `rounded-xl` where the block carries multi-line text or structured details. Collapsed state: +- Preserve the existing `432px` compact width. - Keep the compact width list-focused, with enough height to reveal roughly six recent rows before scrolling. - Show recent conditions only. Do not show scheduled work orders in the collapsed state; they are secondary to the compact scanning task. - The header should reflect the compact content, such as recent-condition count. Avoid advertising hidden scheduled-work content unless the expanded state is open. @@ -477,6 +407,7 @@ Collapsed state: Expanded state: +- Preserve the existing `880px` width and `960px` width at `1536px` and above. - Add a separate detail region beside the timeline list. The detail region may use the newly available horizontal space for evidence, Agent analysis, recommended options, and operational metadata. - Keep the timeline list column width and row typography close to the collapsed state. The list should feel like the same instrument after expansion. - Show scheduled work orders as a separate compact group above recent conditions only in expanded state. If there are many work orders, keep that group scrollable and height-limited. @@ -535,14 +466,14 @@ Reusable controls under `features/map/core/components` should share one compact Zoom, toolbar, scaleline, and other always-visible map controls should use the `control` elevation level: -- Surface: use `surface-map-control`, fixed at `rgba(255, 255, 255, 0.92)`. -- Border: white at roughly 70-80% opacity plus a very subtle dark ring when extra definition is needed. -- Shadow: soft multi-layer map shadow, not card-like depth. -- Backdrop blur: use lightly, only when text and icons remain readable. +- Surface: use `acrylic-control` for the outer instrument and solid internal buttons. +- Edge: 1px cool-gray boundary. +- Shadow: the shared restrained acrylic instrument shadow. +- Backdrop blur: apply only to the outer control shell, never to its internal buttons. - Radius: `rounded-xl` for grouped controls; `rounded-lg` for internal icon buttons. -- Do not stack a `command-panel` or second glass wrapper around toolbar, zoom, Agent shell, or scheduled-condition shell. +- Do not stack a second shell around toolbar, zoom, Agent shell, or scheduled-condition shell. -Prefer Tailwind default opacity utilities for UI component surfaces, such as `bg-white/95`, `bg-blue-50/95`, and `shadow-lg shadow-slate-900/20`. Keep explicit color strings only for platform APIs such as MapLibre paint values, runtime dynamic `style` values, syntax-highlighting variables, or rare cases where an audited opacity cannot be represented clearly by Tailwind defaults. +Use semantic surface classes for hierarchy. Tailwind opacity utilities remain appropriate for transient hover and selected-state color, not for defining persistent surfaces. #### Zoom Control @@ -566,7 +497,7 @@ The map toolbar should follow the same visual system as zoom: - Default buttons are flat; hover uses soft blue background and subtle ring. - Active tools use blue fill with white icon/text and no heavy shadow. - Badges may use red only for operational alerts or counts that require attention. -- The rail shell uses `surface-map-control` (`rgba(255, 255, 255, 0.92)`) and should not sit inside another translucent panel. +- The rail shell uses `acrylic-control` and should not sit inside another elevated panel. Opening a tool rail panel should not move the map camera by default. Treat tool panels as floating overlays unless they become persistent major panels that materially obstruct map work. @@ -578,8 +509,8 @@ 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.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. +- Toolbar function panels use `acrylic-panel` for the outer shell. +- Internal list rows, panel headers, swatches, result blocks, and action rows use `surface-reading` or a flat divider layout. - 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. #### Scaleline @@ -590,7 +521,7 @@ The scaleline should read as a map instrument, not a dashboard footer: - Width should be content-driven, not fixed. - When positioned at the bottom-right edge, it may touch the viewport edge directly. - If touching the bottom-right edge, only the top-left corner should be rounded. -- Use the same `surface-map-control` opacity as toolbar and zoom so the bottom-right instrument cluster reads as one family. +- Use the same `acrylic-control` surface as toolbar and zoom so the bottom-right instrument cluster reads as one family. - Coordinate readout should match the displayed projection. If the scaleline labels `EPSG:3857`, prioritize visible projected `X/Y`; longitude and latitude may remain as secondary debug context, not the primary readout. - Use only a short, smooth transition for the internal scale bar width. Keep metadata widths stable; do not animate the outer auto-sized container or coordinate text. @@ -732,7 +663,7 @@ Primary buttons: Secondary buttons: -- White or translucent fill. +- White or cool-gray solid fill. - Blue or slate text. - Border or subtle ring. - Used for inspect, cancel, locate, details. @@ -875,7 +806,7 @@ The codebase should eventually align to this document rather than letting curren Recommended implementation direction: - Keep route files thin and move feature UI into `features/workbench`, `features/map/core`, and `features/agent`. -- Convert repeated glass surfaces, map controls, metric cards, status pills, and action cards into reusable typed components. +- Keep adaptive acrylic shells and non-filtered reading surfaces behind shared semantic classes instead of repeating raw material values. - Keep MapLibre sources, layers, camera behavior, and interactions outside presentational React components. - Define operational colors as shared constants for both map layers and UI legends. - Treat Agent actions as typed data that the workbench can preview, validate, confirm, apply, and roll back. diff --git a/app/globals.css b/app/globals.css index bdc05c2..d9b79e7 100644 --- a/app/globals.css +++ b/app/globals.css @@ -4,46 +4,50 @@ :root { color-scheme: light; - background: #eaf1f8; - --canvas-map: #eaf1f8; + background: oklch(94.5% 0.012 250); + --canvas-map: oklch(94.5% 0.012 250); --canvas-map-grid: rgba(37, 99, 235, 0.06); - --canvas-panel: rgba(255, 255, 255, 0.94); - --canvas-panel-strong: rgba(255, 255, 255, 0.98); + --acrylic-navigation: rgba(242, 248, 252, 0.74); + --acrylic-panel: rgba(232, 241, 249, 0.58); + --acrylic-control: rgba(249, 251, 255, 0.84); + --acrylic-panel-blur: 22px; + --acrylic-control-blur: 18px; + --acrylic-backdrop-saturation: 1.2; + --acrylic-exclusion: rgba(255, 255, 255, 0.05); + --acrylic-luminosity: rgba(255, 255, 255, 0.22); + --acrylic-noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='.06'/%3E%3C/svg%3E"); + --acrylic-border: rgba(255, 255, 255, 0.58); + --acrylic-outline: rgba(71, 85, 105, 0.18); + --acrylic-highlight: rgba(255, 255, 255, 0.82); + --acrylic-shadow: 0 14px 36px rgba(15, 23, 42, 0.14); + --surface-dock: oklch(97% 0.006 250); + --surface-control: rgba(252, 253, 255, 0.96); + --surface-well: rgba(232, 240, 248, 0.2); + --surface-reading: rgba(255, 255, 255, 0.985); + --glass-transient: rgba(248, 252, 255, 0.88); + --surface-border: oklch(86.5% 0.012 250); + --surface-divider: oklch(90.5% 0.009 250); + --surface-shadow: 0 12px 32px rgba(15, 23, 42, 0.12); + --dock-shadow: 8px 0 24px rgba(15, 23, 42, 0.07); + --radius-icon: 8px; + --radius-control: 12px; + --radius-panel: 16px; + --radius-pill: 9999px; --action-blue: #2563eb; --action-blue-hover: #1d4ed8; --action-blue-soft: #dbeafe; --ink: #0f172a; --ink-secondary: #334155; --ink-muted: #64748b; - --border-soft: rgba(255, 255, 255, 0.62); - --border-strong: rgba(148, 163, 184, 0.34); - --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 25px 50px -12px rgba(23, 37, 84, 0.2); - --glass-air: rgba(255, 255, 255, 0.68); - --glass-navigation: rgba(255, 255, 255, 0.8); - --glass-panel: rgba(255, 255, 255, 0.84); - --glass-readable: rgba(255, 255, 255, 0.9); - --glass-control: rgba(255, 255, 255, 0.9); - --glass-menu: rgba(255, 255, 255, 0.92); - --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); + --border-soft: var(--surface-divider); + --border-strong: var(--surface-border); + --tone-info: oklch(95% 0.035 250); + --tone-normal: oklch(95% 0.04 175); + --tone-warning: oklch(95% 0.05 70); + --tone-danger: oklch(95% 0.045 25); + --tone-agent: oklch(95% 0.035 290); --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%; @@ -66,6 +70,38 @@ --radius: 0.5rem; } +[data-basemap-tone="light"] { + --acrylic-navigation: rgba(242, 248, 252, 0.68); + --acrylic-panel: rgba(232, 241, 249, 0.7); + --acrylic-control: rgba(249, 251, 255, 0.78); + --acrylic-panel-blur: 24px; + --acrylic-control-blur: 20px; + --acrylic-backdrop-saturation: 1.22; + --acrylic-exclusion: rgba(255, 255, 255, 0.04); + --acrylic-luminosity: rgba(255, 255, 255, 0.18); + --surface-control: rgba(252, 253, 255, 0.95); + --surface-well: rgba(232, 240, 248, 0.34); + --surface-reading: rgba(255, 255, 255, 0.97); + --glass-transient: rgba(238, 247, 253, 0.56); +} + +[data-basemap-tone="satellite"] { + --acrylic-navigation: rgba(242, 248, 252, 0.82); + --acrylic-panel: rgba(232, 241, 249, 0.7); + --acrylic-control: rgba(250, 252, 255, 0.9); + --acrylic-panel-blur: 20px; + --acrylic-control-blur: 16px; + --acrylic-backdrop-saturation: 1.1; + --acrylic-exclusion: rgba(255, 255, 255, 0.06); + --acrylic-luminosity: rgba(255, 255, 255, 0.24); + --surface-control: rgba(252, 253, 255, 0.98); + --surface-well: rgba(232, 240, 248, 0.28); + --surface-reading: rgba(255, 255, 255, 0.99); + --glass-transient: rgba(238, 247, 253, 0.72); + --acrylic-outline: rgba(71, 85, 105, 0.24); + --acrylic-shadow: 0 14px 38px rgba(15, 23, 42, 0.2); +} + * { box-sizing: border-box; } @@ -78,9 +114,11 @@ body { body { font-family: - Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", - "PingFang SC", "Microsoft YaHei", sans-serif; + -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", + "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif; color: var(--ink); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } button, @@ -107,208 +145,196 @@ input:focus-visible { auto; } -.command-panel { - background: var(--glass-panel); - box-shadow: - inset 0 1px 0 var(--glass-edge), - var(--shadow-panel); - backdrop-filter: blur(var(--blur-panel)); - -webkit-backdrop-filter: blur(var(--blur-panel)); -} - -.glass-air, -.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; +.acrylic-navigation, +.acrylic-panel, +.acrylic-control, +.surface-dock, +.surface-control, +.surface-well, +.surface-reading, +.glass-transient, +.material-tone-info, +.material-tone-normal, +.material-tone-warning, +.material-tone-danger, +.material-tone-agent { + border-color: var(--surface-border); background-clip: padding-box; } -.glass-air { - background-color: var(--glass-air); +.acrylic-navigation, +.acrylic-panel, +.acrylic-control, +.glass-transient { + border-color: var(--acrylic-border); + background-image: + linear-gradient(var(--acrylic-exclusion), var(--acrylic-exclusion)), + linear-gradient(var(--acrylic-luminosity), var(--acrylic-luminosity)), + var(--acrylic-noise); + background-blend-mode: exclusion, luminosity, soft-light; box-shadow: - inset 0 1px 0 var(--glass-edge), - 0 10px 30px rgba(15, 23, 42, 0.1); - backdrop-filter: blur(var(--blur-air)); - -webkit-backdrop-filter: blur(var(--blur-air)); + inset 0 1px 0 var(--acrylic-highlight), + 0 0 0 1px var(--acrylic-outline), + var(--acrylic-shadow); } -.glass-navigation { - background-color: var(--glass-navigation); +.acrylic-navigation { + background-color: var(--acrylic-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)); - -webkit-backdrop-filter: blur(var(--blur-navigation)); + inset 0 1px 0 var(--acrylic-highlight), + 0 1px 0 var(--acrylic-outline), + 0 8px 24px rgba(15, 23, 42, 0.08); + backdrop-filter: blur(var(--acrylic-panel-blur)) saturate(var(--acrylic-backdrop-saturation)); + -webkit-backdrop-filter: blur(var(--acrylic-panel-blur)) saturate(var(--acrylic-backdrop-saturation)); } -.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)); - -webkit-backdrop-filter: blur(var(--blur-panel)); +.acrylic-panel { + background-color: var(--acrylic-panel); + backdrop-filter: blur(var(--acrylic-panel-blur)) saturate(var(--acrylic-backdrop-saturation)); + -webkit-backdrop-filter: blur(var(--acrylic-panel-blur)) saturate(var(--acrylic-backdrop-saturation)); } -.glass-readable { - background-color: var(--glass-readable); +.acrylic-control { + background-color: var(--acrylic-control); 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)); - -webkit-backdrop-filter: blur(var(--blur-panel)); + inset 0 1px 0 rgba(255, 255, 255, 0.86), + 0 0 0 1px rgba(59, 130, 246, 0.14), + 0 14px 34px rgba(15, 23, 42, 0.13); + backdrop-filter: blur(var(--acrylic-control-blur)) saturate(var(--acrylic-backdrop-saturation)); + -webkit-backdrop-filter: blur(var(--acrylic-control-blur)) saturate(var(--acrylic-backdrop-saturation)); } -.glass-control { - background-color: var(--glass-control); +.surface-dock { background-color: var(--surface-dock); } +.surface-control { background-color: var(--surface-control); } +.surface-well { background-color: var(--surface-well); } +.surface-reading { background-color: var(--surface-reading); } + +.glass-transient { + background-color: var(--glass-transient); box-shadow: - inset 0 1px 0 var(--glass-edge-strong), - var(--shadow-control); - backdrop-filter: blur(var(--blur-control)); - -webkit-backdrop-filter: blur(var(--blur-control)); + inset 0 1px 0 var(--acrylic-highlight), + 0 0 0 1px var(--acrylic-outline), + 0 10px 28px rgba(15, 23, 42, 0.14); + backdrop-filter: blur(var(--acrylic-control-blur)) saturate(var(--acrylic-backdrop-saturation)); + -webkit-backdrop-filter: blur(var(--acrylic-control-blur)) saturate(var(--acrylic-backdrop-saturation)); } -.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)); - -webkit-backdrop-filter: blur(var(--blur-panel)); -} - -.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)); - -webkit-backdrop-filter: blur(var(--blur-focus)); -} - -.glass-info { - background-color: var(--glass-info); +.material-tone-info { + background-color: var(--tone-info); box-shadow: inset 0 1px 0 rgba(147, 197, 253, 0.42); } -.glass-normal { - background-color: var(--glass-normal); +.material-tone-normal { + background-color: var(--tone-normal); box-shadow: inset 0 1px 0 rgba(94, 234, 212, 0.4); } -.glass-warning { - background-color: var(--glass-warning); +.material-tone-warning { + background-color: var(--tone-warning); box-shadow: inset 0 1px 0 rgba(253, 186, 116, 0.46); } -.glass-danger { - background-color: var(--glass-danger); +.material-tone-danger { + background-color: var(--tone-danger); box-shadow: inset 0 1px 0 rgba(252, 165, 165, 0.48); } -.glass-agent { - background-color: var(--glass-agent); +.material-tone-agent { + background-color: var(--tone-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-air, - .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; + .acrylic-navigation, + .acrylic-panel, + .acrylic-control, + .glass-transient { + background-color: rgba(250, 252, 255, 0.98); + background-image: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; } } @media (prefers-reduced-transparency: reduce) { - .glass-air, - .glass-navigation, - .glass-panel, - .glass-readable, - .glass-control, - .glass-menu, - .glass-focus, - .command-panel { - background-color: var(--surface-solid); + .acrylic-navigation, + .acrylic-panel, + .acrylic-control, + .glass-transient { + background-color: rgba(250, 252, 255, 0.98); + background-image: none; backdrop-filter: none; -webkit-backdrop-filter: none; } - .glass-info { + .surface-well, + .surface-control, + .surface-reading { + background-color: rgba(255, 255, 255, 0.98); + background-image: none; + } + + .material-tone-info { background-color: #eff6ff; } - .glass-normal { + .material-tone-normal { background-color: #f0fdfa; } - .glass-warning { + .material-tone-warning { background-color: #fff7ed; } - .glass-danger { + .material-tone-danger { background-color: #fef2f2; } - .glass-agent { + .material-tone-agent { background-color: #f5f3ff; } } +@media (forced-colors: active) { + .acrylic-navigation, + .acrylic-panel, + .acrylic-control, + .glass-transient, + .surface-dock, + .surface-control, + .surface-well, + .surface-reading, + .material-tone-info, + .material-tone-normal, + .material-tone-warning, + .material-tone-danger, + .material-tone-agent { + forced-color-adjust: none; + border-color: CanvasText; + background-color: Canvas; + background-image: none; + box-shadow: none; + color: CanvasText; + backdrop-filter: none; + -webkit-backdrop-filter: none; + } +} + .agent-panel-shell { - border-color: rgba(148, 163, 184, 0.34); - background: #f8fafc; - box-shadow: - 0 24px 64px rgba(15, 23, 42, 0.18), - 0 2px 8px rgba(15, 23, 42, 0.08); - backdrop-filter: none; - -webkit-backdrop-filter: none; + border-color: var(--acrylic-border); } .agent-panel-band { - background: #ffffff; + background: var(--surface-control); } .agent-panel-conversation { - background: #f8fafc; + background: var(--surface-well); } .agent-panel-message { - border: 1px solid #e2e8f0; - background: #ffffff; + border: 1px solid var(--surface-divider); + background: var(--surface-reading); } .agent-streaming-response { @@ -333,18 +359,18 @@ input:focus-visible { } .agent-panel-nested { - border: 1px solid #e2e8f0; - background: #f8fafc; + border: 1px solid var(--surface-divider); + background: var(--surface-control); } .agent-panel-control { - border: 1px solid #e2e8f0; - background: #ffffff; + border: 1px solid rgba(100, 116, 139, 0.16); + background: var(--surface-reading); } .agent-panel-icon-button { - border: 1px solid #e2e8f0; - background: #f8fafc; + border: 1px solid var(--surface-divider); + background: var(--surface-control); box-shadow: none; } @@ -447,7 +473,6 @@ input:focus-visible { scheduled-feed-shell-show 170ms cubic-bezier(0.22, 1, 0.36, 1), agent-panel-fade 140ms ease-out; transform-origin: top right; - will-change: opacity, transform; } .scheduled-feed-shell-exit { @@ -456,12 +481,11 @@ input:focus-visible { agent-panel-dim 140ms ease-in forwards; pointer-events: none; transform-origin: top right; - will-change: opacity, transform; } .agent-panel-confirmation { - border: 1px solid rgba(254, 215, 170, 0.92); - background: rgba(255, 247, 237, 0.96); + border: 1px solid rgba(253, 186, 116, 0.46); + background: rgba(255, 247, 237, 0.82); } .agent-panel-enter { @@ -653,11 +677,11 @@ input:focus-visible { transform: none; } - .scheduled-feed-layout { + .scheduled-feed-panel-shell { transition: none; } - .scheduled-feed-panel-shell { + .scheduled-feed-layout { transition: none; } @@ -678,9 +702,9 @@ input:focus-visible { } .maplibregl-ctrl-group { - border: 1px solid rgba(255, 255, 255, 0.7) !important; - border-radius: 14px !important; - box-shadow: 0 14px 36px rgba(30, 64, 115, 0.14) !important; + border: 1px solid var(--surface-border) !important; + border-radius: var(--radius-control) !important; + box-shadow: var(--surface-shadow) !important; overflow: hidden; } diff --git a/features/agent/components/agent-collapsed-rail.tsx b/features/agent/components/agent-collapsed-rail.tsx index c8096ed..62104b4 100644 --- a/features/agent/components/agent-collapsed-rail.tsx +++ b/features/agent/components/agent-collapsed-rail.tsx @@ -3,9 +3,7 @@ import { ChevronRight } from "lucide-react"; import type { PersonaState } from "@/shared/ai-elements/persona"; import { cn } from "@/lib/utils"; -import { - MAP_MAJOR_PANEL_RADIUS_CLASS_NAME -} from "@/features/map/core/components/map-control-styles"; +import { MAP_MAJOR_PANEL_RADIUS_CLASS_NAME } from "@/features/map/core/components/map-control-styles"; import { AgentPersona } from "./agent-persona"; type AgentCollapsedRailProps = { @@ -16,13 +14,13 @@ type AgentCollapsedRailProps = { export function AgentCollapsedRail({ personaState, statusLabel = "Agent", onExpand }: AgentCollapsedRailProps) { return ( -