1918 lines
42 KiB
CSS
1918 lines
42 KiB
CSS
/**
|
|
* Sidebar styles. Per the DSH web-styling rules every visual value rides the
|
|
* theme: --dsw-alias-* semantic tokens for colors, --dsw-font-* typography
|
|
* roles (size paired with line-height), and --ds-* for motion. The chrome
|
|
* mirrors the native DSH sidebar: flat (NO box-shadows anywhere), the
|
|
* sidebar fill surface, hairline borders, and the 28/36px circular icon
|
|
* controls with hover fills. No static palette values, no color literals, no
|
|
* theme selectors — light/dark adaptation is the theme package's job.
|
|
*/
|
|
|
|
/* ── Shell ─────────────────────────────────────────────────────────────── */
|
|
|
|
/* The persistent expand/collapse cluster at the top-right corner: two rail
|
|
controls side by side (bottom panel's glyph LEFT of the right panel's).
|
|
Always pinned to the viewport corner — inside the right panel's top-right
|
|
while it is open, sitting flush in the 34px tab strip (28px buttons at
|
|
top: 3) whose right end it really squeezes. */
|
|
.toggleCluster {
|
|
position: fixed;
|
|
top: 3px;
|
|
right: 10px;
|
|
z-index: 55;
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 4px;
|
|
}
|
|
|
|
/* The tab strip of the OPEN right panel reserves the cluster's width at its
|
|
right end, so the tabs and the + menu genuinely yield space to the cluster
|
|
(never hiding under it). */
|
|
.panel:not(.panelHidden) .tabBar {
|
|
padding-right: 72px;
|
|
}
|
|
|
|
/* The closed-state affordance is the native rail icon control: a plain
|
|
28px circle (the app's icon-button size — the cluster must stay inside
|
|
the 34px tab-strip band so it never covers the pane headers below),
|
|
transparent fill, secondary ink; hover raises the fill. No border, no
|
|
shadow — the icon alone is the affordance. */
|
|
.toggleButton {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
transition:
|
|
background var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
color var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
}
|
|
|
|
.toggleButton:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.toggleButton:disabled {
|
|
opacity: 0.4;
|
|
cursor: default;
|
|
}
|
|
|
|
.panel {
|
|
position: fixed;
|
|
top: 0;
|
|
right: 0;
|
|
/* Full height: the bottom panel squeezes only the center column, so the
|
|
right panel never gives up any vertical position. */
|
|
bottom: 0;
|
|
z-index: 50;
|
|
display: flex;
|
|
flex-direction: column;
|
|
/* The native sidebar surface: bluish-50 over white / bluish-900 over dark,
|
|
exactly like the app's own sidebar column (SidebarRoot .root). */
|
|
background: var(--dsw-specific-sidebar-fill);
|
|
border-left: 1px solid var(--dsw-alias-border-l2);
|
|
/* Expand/collapse slides the panel; fullscreen animates the width. */
|
|
transition:
|
|
transform var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
width var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
}
|
|
|
|
/* Collapsed: slid off-screen, interactive nothing, hidden after the slide. */
|
|
.panelHidden {
|
|
transform: translateX(102%);
|
|
pointer-events: none;
|
|
visibility: hidden;
|
|
transition:
|
|
transform var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
width var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
visibility 0s linear var(--ds-transition-duration-slow);
|
|
}
|
|
|
|
/* Width drags write at pointer cadence; easing would detach the edge. */
|
|
.panel[data-dragging] {
|
|
transition: none;
|
|
}
|
|
|
|
.panelResize {
|
|
position: absolute;
|
|
left: -4px;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 8px;
|
|
cursor: col-resize;
|
|
z-index: 2;
|
|
touch-action: none;
|
|
}
|
|
|
|
.panelResizeActive {
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
}
|
|
|
|
.panelBody {
|
|
flex: 1;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
display: flex;
|
|
}
|
|
|
|
/* ── Bottom panel ──────────────────────────────────────────────────────── */
|
|
|
|
/* The bottom panel: an independent workbench at the bottom of the CENTER
|
|
column only (the Sidebar shell writes left/right inline: from the app's
|
|
own left sidebar to the right panel's left edge) — the sidebars never
|
|
give up any position. Same surface as the right panel, with a top
|
|
hairline instead of the left one. */
|
|
.bottomPanel {
|
|
position: fixed;
|
|
bottom: 0;
|
|
z-index: 50;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-specific-sidebar-fill);
|
|
border-top: 1px solid var(--dsw-alias-border-l2);
|
|
/* Expand/collapse slides the panel; height drags animate the height. */
|
|
transition:
|
|
transform var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
height var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
}
|
|
|
|
/* Collapsed: slid off-screen, interactive nothing, hidden after the slide. */
|
|
.bottomPanelHidden {
|
|
transform: translateY(102%);
|
|
pointer-events: none;
|
|
visibility: hidden;
|
|
transition:
|
|
transform var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
height var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
visibility 0s linear var(--ds-transition-duration-slow);
|
|
}
|
|
|
|
/* Height drags write at pointer cadence; easing would detach the edge. */
|
|
.bottomPanel[data-dragging] {
|
|
transition: none;
|
|
}
|
|
|
|
/* The bottom panel's height drag: the top edge strip (mirror of the right
|
|
panel's width strip). */
|
|
.bottomResize {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
top: -4px;
|
|
height: 8px;
|
|
cursor: row-resize;
|
|
z-index: 2;
|
|
touch-action: none;
|
|
}
|
|
|
|
.bottomResizeActive {
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
}
|
|
|
|
/* The bottom panel's close control: the app's icon-button pattern (28px
|
|
circle), pinned to the tab strip's right end — one tap collapses the
|
|
panel. The tab strips below reserve its width so the + menu never hides
|
|
under it. */
|
|
.bottomClose {
|
|
position: absolute;
|
|
top: 3px;
|
|
right: 6px;
|
|
z-index: 4;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
flex: none;
|
|
}
|
|
|
|
.bottomClose:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* The bottom panel's tab strips reserve the close control's width at their
|
|
right end, so the + menu never hides under it. */
|
|
.bottomPanel .tabBar {
|
|
padding-right: 40px;
|
|
}
|
|
|
|
/* The shared corner (both panels open): the intersection of the right
|
|
panel's left edge and the bottom panel's top edge. Horizontal drags
|
|
resize the width, vertical drags the height — the two panels drag
|
|
against each other. */
|
|
.cornerHandle {
|
|
position: fixed;
|
|
width: 12px;
|
|
height: 12px;
|
|
z-index: 52;
|
|
cursor: nwse-resize;
|
|
touch-action: none;
|
|
}
|
|
|
|
.cornerHandle:hover,
|
|
.cornerHandle[data-dragging] {
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
}
|
|
|
|
/* Icon button: the app's own icon-button pattern (ui-workspace) — a 28px
|
|
circle, secondary label, hover fill; no border so the icon alone is the
|
|
affordance. */
|
|
.iconButton {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 28px;
|
|
height: 28px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
flex: none;
|
|
}
|
|
|
|
.iconButton:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.iconButton:disabled {
|
|
opacity: 0.4;
|
|
cursor: default;
|
|
}
|
|
|
|
/* ── Workbench / split panes ───────────────────────────────────────────── */
|
|
|
|
.workbench {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
}
|
|
|
|
.split {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
}
|
|
|
|
.splitRow {
|
|
flex-direction: row;
|
|
}
|
|
|
|
.splitCol {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.splitChild {
|
|
position: relative;
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.divider {
|
|
position: relative;
|
|
flex: none;
|
|
z-index: 3;
|
|
touch-action: none;
|
|
}
|
|
|
|
/* The hit area stays 7px wide enough to grab, but the visual is a 1px
|
|
hairline centered in it — a divider line, not a thick band. */
|
|
.dividerRow::after,
|
|
.dividerCol::after {
|
|
content: '';
|
|
position: absolute;
|
|
background: var(--dsw-alias-border-l2);
|
|
transition: background var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
}
|
|
|
|
.dividerRow {
|
|
width: 7px;
|
|
margin: 0 -2px;
|
|
cursor: col-resize;
|
|
}
|
|
|
|
.dividerRow::after {
|
|
top: 0;
|
|
bottom: 0;
|
|
left: 50%;
|
|
width: 1px;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
.dividerCol {
|
|
height: 7px;
|
|
margin: -2px 0;
|
|
cursor: row-resize;
|
|
}
|
|
|
|
.dividerCol::after {
|
|
left: 0;
|
|
right: 0;
|
|
top: 50%;
|
|
height: 1px;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
.divider:hover::after,
|
|
.dividerActive::after {
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
}
|
|
|
|
.pane {
|
|
position: relative;
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.paneDrop {
|
|
outline: 1px solid var(--dsw-alias-interactive-bg-hover-accent);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
/* VSCode-style drag-to-edge drop zones (25% edges, 50% center). */
|
|
.dropOverlay {
|
|
position: absolute;
|
|
z-index: 6;
|
|
pointer-events: none;
|
|
background: var(--dsw-alias-interactive-bg-hover-accent);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.dropLeft {
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 25%;
|
|
}
|
|
|
|
.dropRight {
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 25%;
|
|
}
|
|
|
|
.dropUp {
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 25%;
|
|
}
|
|
|
|
.dropDown {
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 25%;
|
|
}
|
|
|
|
.dropCenter {
|
|
left: 25%;
|
|
right: 25%;
|
|
top: 25%;
|
|
bottom: 25%;
|
|
background: transparent;
|
|
outline: 2px dashed var(--dsw-alias-interactive-bg-hover-accent);
|
|
outline-offset: -2px;
|
|
}
|
|
|
|
.paneContent {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* One tab's mounted content; inactive tabs stay mounted but hidden so
|
|
switching never tears down terminals/editors (state survives). */
|
|
.paneTab {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.paneTabHidden {
|
|
display: none;
|
|
}
|
|
|
|
/* An empty pane's welcome cards: a responsive grid of compact cards — as
|
|
many per row as the pane width allows; rows that cannot fit the pane
|
|
height are clipped (never scrolled or squeezed smaller). */
|
|
.paneEmptyCards {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
align-content: start;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.paneCard {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
padding: 12px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 8px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
color: var(--dsw-alias-label-secondary);
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.paneCard:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
border-color: var(--dsw-alias-border-l2);
|
|
}
|
|
|
|
.paneCard:disabled {
|
|
opacity: 0.45;
|
|
cursor: default;
|
|
}
|
|
|
|
/* ── Tab strip ─────────────────────────────────────────────────────────── */
|
|
|
|
.tabBar {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: stretch;
|
|
height: 34px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
}
|
|
|
|
.tabBarDrop {
|
|
outline: 1px dashed var(--dsw-alias-interactive-bg-hover-accent);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
.tabList {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
overflow-x: auto;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.tabList::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.tab {
|
|
flex: none;
|
|
max-width: 160px;
|
|
min-width: 64px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 0 4px 0 10px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-secondary);
|
|
border-right: 1px solid var(--dsw-alias-border-l1);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
}
|
|
|
|
.tab:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.tabActive {
|
|
color: var(--dsw-alias-label-primary);
|
|
/* Native selected fill (Rows .sessionRow.selected) — no underline, no shadow. */
|
|
background: var(--dsw-alias-interactive-bg-active);
|
|
}
|
|
|
|
.tabTitle {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tabClose {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 18px;
|
|
height: 18px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
cursor: pointer;
|
|
flex: none;
|
|
}
|
|
|
|
.tabClose:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.tabBarPlus {
|
|
flex: none;
|
|
align-self: center;
|
|
/* Adjacent to the rightmost tab; sticky so overflowing tab rows keep it
|
|
visible at the right edge of the scrollport. */
|
|
position: sticky;
|
|
right: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 22px;
|
|
height: 22px;
|
|
margin: 0 6px;
|
|
padding: 0;
|
|
border: none;
|
|
border-radius: 5px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tabBarPlus:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* ── Explorer ──────────────────────────────────────────────────────────── */
|
|
|
|
.explorer {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.explorerHeader {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
height: 36px;
|
|
padding: 0 8px 0 12px;
|
|
}
|
|
|
|
.explorerRoot {
|
|
font: var(--dsw-font-s-14);
|
|
color: var(--dsw-alias-label-secondary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.explorerBody {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
padding: 2px 6px 8px;
|
|
}
|
|
|
|
/* One tree row: the app's session-row cell (34px, radius 8, inset, hover
|
|
fill, 22px indent step, 14px ink) with a mount fade so expanding a
|
|
directory reveals its children gently (stable keys keep existing rows from
|
|
replaying it). */
|
|
.explorerRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
width: 100%;
|
|
height: 34px;
|
|
padding: 0 8px;
|
|
border: none;
|
|
background: transparent;
|
|
font: var(--dsw-font-s-14);
|
|
color: var(--dsw-alias-label-primary);
|
|
text-align: left;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
border-radius: 8px;
|
|
animation: dsh-row-in 150ms var(--ds-ease-in-out);
|
|
}
|
|
|
|
.explorerRow:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.explorerDir {
|
|
font: var(--dsw-font-s-strong-14);
|
|
}
|
|
|
|
.explorerHidden {
|
|
opacity: 0.45;
|
|
}
|
|
|
|
.explorerName {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
/* The hover-revealed @-reference button at the row's far right (and the
|
|
transient "copied" label that replaces it after a copy). Hidden rows
|
|
keep the name at full width; hovering or focusing the row reveals it. */
|
|
.explorerRef {
|
|
flex: none;
|
|
display: none;
|
|
align-items: center;
|
|
height: 20px;
|
|
padding: 0 8px;
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 999px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.explorerRef:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.explorerRow:hover .explorerRef,
|
|
.explorerRow:focus-within .explorerRef {
|
|
display: inline-flex;
|
|
}
|
|
|
|
.explorerCopied {
|
|
flex: none;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.explorerError {
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-state-error-primary);
|
|
cursor: default;
|
|
}
|
|
|
|
/* Mount fade for tree rows (the app's session-row reveal). */
|
|
@keyframes dsh-row-in {
|
|
from { opacity: 0; }
|
|
}
|
|
|
|
.explorerEmpty {
|
|
padding: 16px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
text-align: center;
|
|
}
|
|
|
|
/* ── Editor ────────────────────────────────────────────────────────────── */
|
|
|
|
.editor {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.editorHeader {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 8px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.editorTitle {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
color: var(--dsw-alias-label-secondary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.editorStatus {
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.editorStatusError {
|
|
color: var(--dsw-alias-state-error-primary);
|
|
}
|
|
|
|
.dirtyDot {
|
|
width: 7px;
|
|
height: 7px;
|
|
border-radius: 50%;
|
|
background: var(--dsw-alias-state-warn-primary);
|
|
flex: none;
|
|
}
|
|
|
|
.editorPlaceholder {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
padding: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* The unregistered tab type shown by the orphaned-tab placeholder. */
|
|
.orphanedType {
|
|
display: block;
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
opacity: 0.7;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* Binary / download-only files (pptx, OLE .doc/.xls/.ppt, unknown binaries):
|
|
a centered notice plus a download link — the user always gets the file. */
|
|
.editorBinary {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
padding: 24px 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.editorBinaryNotice {
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.editorDownloadLink {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 14px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
transition:
|
|
background var(--ds-transition-duration-slow) var(--ds-ease-in-out),
|
|
border-color var(--ds-transition-duration-slow) var(--ds-ease-in-out);
|
|
}
|
|
|
|
.editorDownloadLink:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
border-color: var(--dsw-alias-border-l2);
|
|
}
|
|
|
|
.editorError {
|
|
padding: 12px 16px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-state-error-primary);
|
|
}
|
|
|
|
.editorBanner {
|
|
flex: none;
|
|
padding: 4px 12px;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-state-warn-label);
|
|
background: var(--dsw-alias-state-warn-tertiary);
|
|
}
|
|
|
|
/* Live sandbox status row (HTML preview / browser tab): GREEN while the
|
|
sandbox is on (with the one-tap temporary unlock), RED while it is off
|
|
(global setting or temporary unlock) — the red state is the persistent
|
|
warning that the page runs on the GUI's own origin. */
|
|
.sandboxStatus {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 4px 10px;
|
|
font: var(--dsw-font-xxxs-11);
|
|
}
|
|
|
|
.sandboxStatusOn {
|
|
color: var(--dsw-alias-label-secondary);
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.sandboxStatusOff {
|
|
color: var(--dsw-alias-state-error-primary);
|
|
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);
|
|
border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 45%, transparent);
|
|
}
|
|
|
|
.sandboxDot {
|
|
flex: none;
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: var(--dsw-alias-state-success-primary);
|
|
}
|
|
|
|
.sandboxStatusOff .sandboxDot {
|
|
background: var(--dsw-alias-state-error-primary);
|
|
}
|
|
|
|
.sandboxStatusText {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.sandboxAction {
|
|
flex: none;
|
|
padding: 2px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
font: inherit;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.sandboxAction:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
/* The HTML preview iframe: fills the editor body. Route-src (never srcdoc)
|
|
so the frame is cross-origin by construction even if the sandbox
|
|
attribute is ever dropped; sandboxed by default (see TextEditor). */
|
|
.editorHtml {
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
border: none;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
/* ── Browser tab ──────────────────────────────────────────────────────── */
|
|
|
|
.browser {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.browserBar {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 6px 8px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.browserInput {
|
|
flex: 1;
|
|
min-width: 0;
|
|
height: 28px;
|
|
padding: 0 10px;
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: var(--dsw-font-xxs-12);
|
|
}
|
|
|
|
.browserInput:focus {
|
|
outline: none;
|
|
border-color: var(--dsw-alias-border-l2);
|
|
}
|
|
|
|
.browserMessage {
|
|
flex: none;
|
|
padding: 4px 12px;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-state-warn-label);
|
|
background: var(--dsw-alias-state-warn-tertiary);
|
|
}
|
|
|
|
.browserFrame {
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
border: none;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.browserStart {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
text-align: center;
|
|
font: var(--dsw-font-xs-13);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* Embed-refusal panel (X-Frame-Options / frame-ancestors): replaces the
|
|
blank refused iframe with the reason + open-in-browser actions. */
|
|
.browserBlocked {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 6px;
|
|
padding: 24px;
|
|
text-align: center;
|
|
color: var(--dsw-alias-state-warn-primary);
|
|
}
|
|
|
|
.browserBlockedTitle {
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.browserBlockedDesc {
|
|
max-width: 280px;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.browserBlockedActions {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.browserBlockedButton {
|
|
padding: 4px 12px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: var(--dsw-font-xxxs-11);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.browserBlockedButton:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.editorCm {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
background: transparent;
|
|
}
|
|
|
|
.editorCmHidden {
|
|
display: none;
|
|
}
|
|
|
|
/* CodeMirror fills the pane; the theme supplies its own token palette
|
|
(a designed dark theme, same policy as the xterm palette). */
|
|
.editorCm :global(.cm-editor) {
|
|
height: 100%;
|
|
}
|
|
|
|
.editorCm :global(.cm-editor.cm-focused) {
|
|
outline: none;
|
|
}
|
|
|
|
/* Preview / edit segmented toggle (markdown and other previewable files). */
|
|
.editorModeToggle {
|
|
flex: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
padding: 2px;
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
}
|
|
|
|
.editorModeButton {
|
|
padding: 2px 8px;
|
|
border: none;
|
|
border-radius: 4px;
|
|
background: transparent;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
font: var(--dsw-font-xxxs-11);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.editorModeButton:hover {
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.editorModeActive {
|
|
background: var(--dsw-alias-bg-base);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.editorImageWrap {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: auto;
|
|
padding: 12px;
|
|
}
|
|
|
|
.editorImage {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.editorMd {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
padding: 10px 14px;
|
|
font: var(--dsw-font-xs-13);
|
|
}
|
|
|
|
/* The floating "add to conversation" button anchored above a text
|
|
selection in the preview/code viewers. Portalled to document.body and
|
|
position:fixed, so the editor's overflow clips cannot crop it; flat
|
|
chrome like the rest of the sidebar (hairline border, layer-2 fill,
|
|
hover fill — no shadow). */
|
|
.selectionPopup {
|
|
position: fixed;
|
|
z-index: 60;
|
|
transform: translate(-50%, calc(-100% - 8px));
|
|
display: inline-flex;
|
|
align-items: center;
|
|
height: 28px;
|
|
padding: 0 10px;
|
|
border: 1px solid var(--dsw-alias-border-l1);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
white-space: nowrap;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.selectionPopup:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
/* Word preview: the document scrolls independently while the zoom control
|
|
stays pinned to the bottom edge. */
|
|
.editorDocx {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorDocxViewport {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: auto;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
/* docx-preview renders into a wrapper div with .docx className; the host
|
|
above scrolls while the wrapper holds the page flow. */
|
|
.editorDocxWrap {
|
|
flex: none;
|
|
padding: 16px;
|
|
}
|
|
|
|
.editorDocxZoom {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 34px;
|
|
padding: 4px 10px;
|
|
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
}
|
|
|
|
.editorDocxZoomHint,
|
|
.editorDocxZoomValue {
|
|
flex: none;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.editorDocxZoomValue {
|
|
width: 36px;
|
|
text-align: right;
|
|
}
|
|
|
|
.editorDocxZoomRange {
|
|
flex: 1;
|
|
min-width: 72px;
|
|
accent-color: var(--dsw-alias-brand-primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.editorPdf {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorPdfToolbar {
|
|
flex: none;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding: 6px 8px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.editorPdfStage {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
}
|
|
|
|
.editorPdfFrame {
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
border: none;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorPdfFrameBlocked {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.editorPdfDragShield {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 4;
|
|
pointer-events: none;
|
|
background: transparent;
|
|
}
|
|
|
|
.editorPdfDragShieldActive {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
/* Tab drag must switch iframe hit-testing before the pointer can enter the
|
|
Chromium PDF browsing context. The body flag is set synchronously by the
|
|
drag source, avoiding React render timing entirely. */
|
|
:global(body[data-dsh-tab-dragging]) .editorPdfFrame {
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
:global(body[data-dsh-tab-dragging]) .editorPdfDragShield {
|
|
pointer-events: auto !important;
|
|
}
|
|
|
|
.editorXlsx {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorUniverHost {
|
|
width: 100%;
|
|
height: 100%;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
}
|
|
|
|
.editorOfficeOverlay {
|
|
position: absolute;
|
|
inset: 0;
|
|
z-index: 2;
|
|
display: flex;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorPptx {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.editorPptxToolbar {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 6px 8px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.editorPptxToolbar .editorDownloadLink {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.editorPptxButton {
|
|
height: 28px;
|
|
padding: 0 10px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-secondary);
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.editorPptxButton:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.editorPptxButton:disabled {
|
|
opacity: 0.4;
|
|
cursor: default;
|
|
}
|
|
|
|
.editorPptxPosition {
|
|
min-width: 64px;
|
|
text-align: center;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.editorPptxStage {
|
|
position: relative;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.editorPptxHost {
|
|
width: 100%;
|
|
height: 100%;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
overflow: auto;
|
|
}
|
|
|
|
/* ── Terminal ──────────────────────────────────────────────────────────── */
|
|
|
|
.terminalWrap {
|
|
flex: 1;
|
|
min-height: 0;
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--dsw-alias-bg-base);
|
|
}
|
|
|
|
.terminal {
|
|
flex: 1;
|
|
min-height: 0;
|
|
padding: 6px 4px 6px 8px;
|
|
}
|
|
|
|
.terminal :global(.xterm) {
|
|
height: 100%;
|
|
}
|
|
|
|
.terminalBanner {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
padding: 3px 10px;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-state-warn-label);
|
|
background: var(--dsw-alias-state-warn-tertiary);
|
|
}
|
|
|
|
/* The attempted ws URL, shown when the connection failed (diagnosis). */
|
|
.terminalBannerUrl {
|
|
flex-basis: 100%;
|
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
word-break: break-all;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.boundaryError {
|
|
position: fixed;
|
|
right: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
z-index: 50;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
align-items: flex-start;
|
|
padding: 16px;
|
|
background: var(--dsw-alias-bg-layer-1);
|
|
border-left: 1px solid var(--dsw-alias-border-l2);
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-state-error-primary);
|
|
overflow: auto;
|
|
}
|
|
|
|
.terminalRetry {
|
|
flex: none;
|
|
padding: 1px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 999px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-secondary);
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.terminalRetry:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* ── Git ───────────────────────────────────────────────────────────────── */
|
|
|
|
.git {
|
|
flex: 1;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
/* Tab pages never scroll horizontally: content wraps or truncates to the
|
|
panel width instead of panning sideways. */
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.gitHeader {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
height: 36px;
|
|
padding: 0 8px 0 12px;
|
|
}
|
|
|
|
.gitBranchSelect {
|
|
flex: 1;
|
|
min-width: 0;
|
|
height: 26px;
|
|
padding: 0 6px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-bg-base);
|
|
color: var(--dsw-alias-label-primary);
|
|
font: var(--dsw-font-xxs-12);
|
|
}
|
|
|
|
.gitSection {
|
|
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.gitSectionHeader {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 6px 12px 4px;
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.gitLink {
|
|
border: none;
|
|
background: transparent;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-brand-primary);
|
|
cursor: pointer;
|
|
padding: 0;
|
|
}
|
|
|
|
.gitLink:hover:not(:disabled) {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.gitLink:disabled {
|
|
opacity: 0.4;
|
|
cursor: default;
|
|
}
|
|
|
|
.gitRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-height: 34px;
|
|
margin: 0 6px;
|
|
padding: 0 8px;
|
|
border-radius: 8px;
|
|
animation: dsh-row-in 150ms var(--ds-ease-in-out);
|
|
}
|
|
|
|
.gitRow:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.gitRowSelected {
|
|
background: var(--dsw-alias-interactive-bg-active);
|
|
}
|
|
|
|
.gitRowMain {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
border: none;
|
|
background: transparent;
|
|
padding: 3px 0;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
|
|
.gitBadge {
|
|
flex: none;
|
|
width: 20px;
|
|
height: 16px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: 4px;
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.gitName {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: var(--dsw-font-s-14);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.gitEmpty {
|
|
padding: 4px 12px 8px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.gitPlaceholder {
|
|
padding: 16px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
text-align: center;
|
|
}
|
|
|
|
.gitError {
|
|
padding: 8px 12px;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-state-error-primary);
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.gitDiff {
|
|
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
padding: 8px;
|
|
}
|
|
|
|
/* The diff tab (a change opened from the git panel, like VSCode's diff
|
|
editor): header + scrollable diff body, no horizontal panning. */
|
|
.gitDiffTab {
|
|
flex: 1;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.gitDiffTabHeader {
|
|
flex: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
height: 36px;
|
|
padding: 0 8px 0 12px;
|
|
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
|
}
|
|
|
|
.gitDiffTabTitle {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
/* One changed file's section: path header, hunk headers, aligned lines. */
|
|
.gitDiffFile {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 6px;
|
|
padding: 8px 2px 2px;
|
|
}
|
|
|
|
.gitDiffFilePath {
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
color: var(--dsw-alias-label-primary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.gitDiffFileOld {
|
|
flex: none;
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 40%;
|
|
}
|
|
|
|
.gitDiffFileTag {
|
|
flex: none;
|
|
padding: 0 6px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 999px;
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.gitDiffHunk {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 3px 2px;
|
|
font: var(--dsw-font-markdown-code-block-small);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.gitDiffHunkHeader {
|
|
flex: none;
|
|
color: var(--dsw-alias-label-secondary);
|
|
}
|
|
|
|
.gitDiffHunkSection {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* One aligned diff line: old gutter / new gutter / code. Lines WRAP instead
|
|
of scrolling horizontally (the tab-page rule: fill the width adaptively),
|
|
with the long-token fallback breaking anywhere a space never comes. */
|
|
.gitDiffLine {
|
|
display: flex;
|
|
align-items: stretch;
|
|
font: var(--dsw-font-markdown-code-block-small);
|
|
line-height: 20px;
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
min-width: 0;
|
|
}
|
|
|
|
.gitDiffNum {
|
|
flex: none;
|
|
width: 36px;
|
|
padding-right: 8px;
|
|
text-align: right;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
user-select: none;
|
|
}
|
|
|
|
.gitDiffCode {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: visible;
|
|
}
|
|
|
|
.gitDiffCtx {
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.gitDiffDel {
|
|
color: var(--dsw-alias-state-error-primary);
|
|
background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent);
|
|
}
|
|
|
|
.gitDiffAdd {
|
|
color: var(--dsw-alias-state-success-primary);
|
|
background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent);
|
|
}
|
|
|
|
.gitDiffMeta {
|
|
padding-left: 2px;
|
|
}
|
|
|
|
.gitDiffMetaText {
|
|
font: var(--dsw-font-xxxs-11);
|
|
font-style: italic;
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.gitDiffExpand {
|
|
display: block;
|
|
width: 100%;
|
|
margin: 4px 0;
|
|
border: none;
|
|
background: transparent;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-brand-primary);
|
|
cursor: pointer;
|
|
text-align: center;
|
|
}
|
|
|
|
.gitDiffExpand:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.gitConfirmDesc {
|
|
margin: 0;
|
|
font: var(--dsw-font-s-14);
|
|
color: var(--dsw-alias-label-primary);
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.gitCommit {
|
|
display: flex;
|
|
gap: 6px;
|
|
padding: 8px 12px;
|
|
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
align-items: center;
|
|
}
|
|
|
|
.gitCommitInput {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.gitCommitButton {
|
|
flex: none;
|
|
height: 26px;
|
|
padding: 0 12px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
background: var(--dsw-alias-button-primary-fill);
|
|
color: var(--dsw-alias-label-primary-inverted);
|
|
font: var(--dsw-font-xxs-strong-12);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gitCommitButton:hover:not(:disabled) {
|
|
background: var(--dsw-alias-button-primary-hover);
|
|
}
|
|
|
|
.gitCommitButton:disabled {
|
|
opacity: 0.45;
|
|
cursor: default;
|
|
}
|
|
|
|
/* A history row: line 1 = hash + subject, line 2 = ref badges + author/date
|
|
(badges live on the second line so they never crowd the subject). */
|
|
.gitLogRow {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
padding: 5px 12px;
|
|
cursor: pointer;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.gitLogRow:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
}
|
|
|
|
.gitLogLine1 {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.gitLogHash {
|
|
flex: none;
|
|
font: var(--dsw-font-markdown-code-block-small);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.gitLogLine2 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
min-width: 0;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.gitLogRef {
|
|
flex: none;
|
|
padding: 0 5px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 999px;
|
|
font: var(--dsw-font-xxxs-strong-11);
|
|
color: var(--dsw-alias-brand-primary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.gitLogSubject {
|
|
flex: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
font: var(--dsw-font-s-14);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.gitLogMeta {
|
|
font: var(--dsw-font-xxxs-11);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.gitLogMore {
|
|
display: block;
|
|
width: calc(100% - 24px);
|
|
margin: 4px 12px 8px;
|
|
padding: 6px 0;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 6px;
|
|
background: transparent;
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-secondary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.gitLogMore:hover:not(:disabled) {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.gitLogMore:disabled {
|
|
opacity: 0.5;
|
|
cursor: default;
|
|
}
|
|
|
|
/* ── Intercepted produced-files row ────────────────────────────────────── */
|
|
|
|
.producedRow {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
padding: 4px 0;
|
|
}
|
|
|
|
.producedLabel {
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
.producedChip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
max-width: 200px;
|
|
padding: 2px 8px;
|
|
border: 1px solid var(--dsw-alias-border-l2);
|
|
border-radius: 999px;
|
|
background: var(--dsw-alias-bg-layer-2);
|
|
color: var(--dsw-alias-label-secondary);
|
|
font: var(--dsw-font-xxs-12);
|
|
cursor: pointer;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.producedChip:hover {
|
|
background: var(--dsw-alias-interactive-bg-hover);
|
|
color: var(--dsw-alias-label-primary);
|
|
}
|
|
|
|
.producedChip span {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.producedMore {
|
|
font: var(--dsw-font-xxs-12);
|
|
color: var(--dsw-alias-label-tertiary);
|
|
}
|
|
|
|
/* ── Shared interaction rules ──────────────────────────────────────────── */
|
|
|
|
/* Keyboard focus stays visible on every interactive control (the styling
|
|
policy requires focus visibility; the accent token doubles as the ring). */
|
|
.toggleButton:focus-visible,
|
|
.bottomClose:focus-visible,
|
|
.iconButton:focus-visible,
|
|
.tab:focus-visible,
|
|
.tabClose:focus-visible,
|
|
.tabBarPlus:focus-visible,
|
|
.paneCard:focus-visible,
|
|
.explorerRow:focus-visible,
|
|
.explorerRef:focus-visible,
|
|
.gitRowMain:focus-visible,
|
|
.gitLink:focus-visible,
|
|
.gitCommitButton:focus-visible,
|
|
.gitLogRow:focus-visible,
|
|
.gitLogMore:focus-visible,
|
|
.gitDiffExpand:focus-visible,
|
|
.terminalRetry:focus-visible,
|
|
.editorModeButton:focus-visible,
|
|
.editorDownloadLink:focus-visible,
|
|
.editorPptxButton:focus-visible,
|
|
.editorDocxZoomRange:focus-visible {
|
|
outline: 2px solid var(--dsw-alias-interactive-bg-hover-accent);
|
|
outline-offset: -1px;
|
|
}
|
|
|
|
/* Reduced motion: no slides, fades or color transitions. */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.panel,
|
|
.panelHidden,
|
|
.bottomPanel,
|
|
.bottomPanelHidden,
|
|
.toggleCluster,
|
|
.toggleButton,
|
|
.tab,
|
|
.tabBarPlus,
|
|
.paneCard,
|
|
.explorerRow,
|
|
.gitRow,
|
|
.divider,
|
|
.dividerRow::after,
|
|
.dividerCol::after {
|
|
transition: none;
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* ── Narrow (mobile) viewport ──────────────────────────────────────────── */
|
|
|
|
/* Pairs with NARROW_MAX_WIDTH = 768 in src/client/breakpoints.ts: widths
|
|
strictly below 768px (i.e. max-width: 767px) switch the sidebar to the
|
|
mobile layout — one full-screen drawer (the bottom panel's tabs were
|
|
migrated into the right tree), one toggle button. The structural
|
|
switches (which panels render, the migration) are JS-driven via
|
|
useNarrowViewport; this block only adapts the pure style details. */
|
|
@media (max-width: 767px) {
|
|
/* The toggle cluster holds a single button on narrow viewports (the
|
|
bottom panel button is not rendered), so the open panel's tab strip
|
|
reserves 40px instead of the desktop 72px — the tabs and + menu still
|
|
yield space to the one remaining control. */
|
|
.panel:not(.panelHidden) .tabBar {
|
|
padding-right: 40px;
|
|
}
|
|
|
|
/* Narrower tabs: a phone-width drawer fits more open tabs before the
|
|
strip scrolls. */
|
|
.tab {
|
|
min-width: 48px;
|
|
max-width: 128px;
|
|
}
|
|
}
|