Files
BrowserSkill/packages/dsh-plugin-browserskill/src/client/ObservationOverlay.module.css
T
BB-fat a004291848 feat(dsh-plugin): better-sidebar 集成、会话关停与归档清理、悬浮窗修复 (#130)
* better-sidebar 集成:observation 视图迁入 better-sidebar 载体,按会话作用域展示 bsk 会话
* 会话关停与归档清理:支持会话 dispose 与归档自动清理
* 悬浮窗修复:悬浮 overlay 拖拽/缩放/PiP 弹出等问题修复
* 修复 Document PiP 空白窗口:载体卸载(关 tab、切会话、插件 HMR)时主动 close() PiP 窗口,避免残留空白窗口
2026-08-25 10:33:46 +08:00

429 lines
8.6 KiB
CSS

/* Observation overlay skin, BrowserSkill family: colors/radius ride the BSK
design tokens (--card/--border/--primary/--destructive/--ring/--radius,
defined on the .bsk-obs scope root by bsk-tokens.nomodule.css — values
mirrored from packages/ui/src/styles/tailwind.css). Layout-only here;
component chrome (buttons etc.) comes from @browser-skill/ui plus the
scope-prefixed utility sheet. The shell overlay layer is click-through, so
the card and capsule opt back into pointer events themselves. */
.card {
position: fixed;
z-index: 40;
display: flex;
flex-direction: column;
pointer-events: auto;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 8px 28px rgb(0 0 0 / 18%);
overflow: hidden;
color: var(--card-foreground);
}
.body {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
font-size: 12px;
background: var(--card);
color: var(--card-foreground);
}
/* Sidebar carrier: fill the better-sidebar tab area and let the body flex
(the tab content container owns the outer sizing). */
.sidebar-tab {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.sidebar-tab > * {
flex: 1;
min-height: 0;
}
/* The BSK product mark in the sidebar tab strip (the source artwork already
carries rounded corners — just soften the square to match). */
.brand-icon {
display: block;
border-radius: 22%;
}
.header {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
user-select: none;
touch-action: none;
border-bottom: 1px solid var(--border);
}
/* Only the floating card drags by its header — the sidebar tab does not. */
.header[data-draggable] {
cursor: move;
}
/* In the sidebar the status row is chrome, not a card edge: drop its bottom
border. A fixed-height bordered row can never track the shell header's
hairline across the panel boundary (its height varies per window), and
the BSK warm border tint clashes with the sidebar's neutral hairlines —
borderless, the stage's own background provides the separation. */
.sidebar-tab .header {
border-bottom: none;
}
.status-text {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
.icon-button {
flex: none;
display: inline-flex;
align-items: center;
border: none;
background: none;
padding: 2px;
border-radius: calc(var(--radius) - 6px);
font: inherit;
color: var(--muted-foreground);
cursor: pointer;
}
.icon-button:hover {
color: var(--foreground);
background: var(--accent);
}
.stage {
position: relative;
flex: 1;
min-height: 0;
display: flex;
align-items: center;
justify-content: center;
background: var(--background);
overflow: hidden;
}
/* Fade only on first mount. The <img> is reused across frames (src swap,
no React key) so this animation must not replay on every breath. */
.thumb {
max-width: 100%;
max-height: 100%;
object-fit: contain;
animation: bsk-obs-fade-in 240ms ease-out;
}
@keyframes bsk-obs-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.placeholder {
color: var(--muted-foreground);
font-size: 12px;
}
.badge {
position: absolute;
top: 6px;
right: 6px;
width: 16px;
height: 16px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
color: oklch(0.7 0.19 60);
border: 1px solid oklch(0.7 0.19 60);
background: var(--card);
}
.actions {
position: relative;
z-index: 3;
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
/* A little inset so icons are not flush with the resize corners. */
padding: 4px 8px;
min-height: 32px;
border-top: 1px solid var(--border);
}
.actions-group {
display: inline-flex;
align-items: center;
gap: 4px;
}
.tool-wrap {
position: relative;
display: inline-flex;
}
.tool-button {
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
border: none;
border-radius: calc(var(--radius) - 4px);
background: none;
color: var(--muted-foreground);
cursor: pointer;
}
.tool-button:disabled {
opacity: 0.4;
cursor: default;
}
.tool-button:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
.tool-danger {
color: var(--destructive);
}
/* Stop session: danger-tinted like interrupt. */
.tool-stop {
color: var(--destructive);
}
/* Hover the wrap (not just :hover on the button) so the tooltip target
lights the icon up too. The corner resize handles sit above this bar, so
the outer sliver of the corner-most buttons starts a resize instead. */
.tool-wrap:hover .tool-button:not(:disabled) {
color: var(--foreground);
background: var(--accent);
}
.tool-wrap:hover .tool-danger:not(:disabled) {
color: var(--destructive);
background: color-mix(in oklch, var(--destructive) 18%, var(--card));
}
.tool-wrap:hover .tool-button:disabled {
background: var(--accent);
}
/* The armed (confirm) state of the stop button: solid destructive fill so
the second click reads as final. Written at the wrap-hover specificity so
the hover rules above never wash it out. */
.tool-button.tool-stop-armed,
.tool-wrap:hover .tool-button.tool-stop-armed {
color: var(--destructive-foreground);
background: var(--destructive);
}
.hint {
position: absolute;
bottom: calc(100% + 4px);
left: 0;
width: max-content;
max-width: 220px;
padding: 4px 6px;
border-radius: calc(var(--radius) - 4px);
background: var(--card);
border: 1px solid var(--border);
color: var(--muted-foreground);
font-size: 11px;
line-height: 1.35;
box-shadow: 0 4px 14px rgb(0 0 0 / 16%);
z-index: 41;
}
.hint[data-align="end"] {
left: auto;
right: 0;
}
/* Invisible hit targets — no grip glyph; all four corners resize. Must sit
above the bottom .actions bar (z-index 3): it spans the full card width
and would otherwise swallow every pointer event aimed at the sw/se
corners. The top corners already win because .header is not positioned. */
.resize-handle {
position: absolute;
width: 16px;
height: 16px;
touch-action: none;
z-index: 4;
}
.resize-handle[data-corner="nw"] {
top: 0;
left: 0;
cursor: nwse-resize;
}
.resize-handle[data-corner="ne"] {
top: 0;
right: 0;
cursor: nesw-resize;
}
.resize-handle[data-corner="sw"] {
bottom: 0;
left: 0;
cursor: nesw-resize;
}
.resize-handle[data-corner="se"] {
bottom: 0;
right: 0;
cursor: nwse-resize;
}
.capsule {
position: fixed;
right: 16px;
top: 64px;
z-index: 40;
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
pointer-events: auto;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--card);
box-shadow: 0 4px 14px rgb(0 0 0 / 16%);
font-size: 12px;
color: var(--muted-foreground);
cursor: pointer;
}
.capsule:hover {
background: var(--accent);
}
.strip {
display: flex;
gap: 6px;
padding: 4px 8px;
overflow-x: auto;
border-top: 1px solid var(--border);
}
.strip-item {
position: relative;
flex: none;
border-radius: calc(var(--radius) - 6px);
border: 1px solid var(--border);
}
.strip-item[data-focused] {
border-color: var(--primary);
}
.strip-item[data-state="error"] {
border-color: var(--destructive);
}
.strip-item[data-state="dead"] {
opacity: 0.45;
}
.strip-main {
display: flex;
align-items: center;
gap: 4px;
padding: 3px 6px;
border: none;
border-radius: calc(var(--radius) - 6px);
background: none;
font: inherit;
font-size: 11px;
color: var(--muted-foreground);
cursor: pointer;
}
.strip-main:hover {
background: var(--accent);
}
.strip-thumb {
width: 28px;
height: 20px;
overflow: hidden;
border-radius: 3px;
background: var(--background);
display: inline-flex;
}
.strip-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.strip-thumb-empty {
width: 100%;
height: 100%;
}
.strip-id {
max-width: 64px;
overflow: hidden;
text-overflow: ellipsis;
}
.pin-badge {
color: var(--primary);
}
.strip-interrupt {
position: absolute;
top: -7px;
right: -7px;
width: 16px;
height: 16px;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px solid var(--destructive);
background: var(--card);
color: var(--destructive);
padding: 0;
cursor: pointer;
}
.strip-interrupt:disabled {
opacity: 0.4;
cursor: default;
}
.icon-button:focus-visible,
.capsule:focus-visible,
.strip-main:focus-visible,
.strip-interrupt:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.thumb {
animation: none;
}
}