mirror of
https://github.com/Tencent/BrowserSkill.git
synced 2026-09-10 13:01:23 +08:00
* better-sidebar 集成:observation 视图迁入 better-sidebar 载体,按会话作用域展示 bsk 会话 * 会话关停与归档清理:支持会话 dispose 与归档自动清理 * 悬浮窗修复:悬浮 overlay 拖拽/缩放/PiP 弹出等问题修复 * 修复 Document PiP 空白窗口:载体卸载(关 tab、切会话、插件 HMR)时主动 close() PiP 窗口,避免残留空白窗口
429 lines
8.6 KiB
CSS
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;
|
|
}
|
|
}
|