305 lines
9.1 KiB
CSS
305 lines
9.1 KiB
CSS
.frame {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
flex: 1 1 auto;
|
|
/* Floating controls query this frame outside the clipped transcript. */
|
|
container-type: inline-size;
|
|
}
|
|
|
|
.root {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 0;
|
|
flex: 1 1 auto;
|
|
|
|
overflow-x: visible;
|
|
overflow-y: clip;
|
|
}
|
|
|
|
.scroll {
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
/* Sides = composer clearance + 16px: on narrow viewports the transcript
|
|
stays exactly 32px narrower than the input card (the shared width rule). */
|
|
padding: 16px calc(var(--dsh-composer-side-clearance) + 16px);
|
|
/* Inline-size query container: wide markdown tables size their breakout
|
|
against this box's content width (100cqw in AssistantMarkdown's
|
|
md-table-wide rule) — the CSS stand-in for deepsuite chat's JS-measured
|
|
--dsl-virtual-list-width. */
|
|
container-type: inline-size;
|
|
}
|
|
|
|
.root[data-chat-following-tail] .scroll,
|
|
:global([data-conversation-scroll]):has(.root[data-chat-following-tail]) {
|
|
overflow-anchor: none;
|
|
}
|
|
|
|
:global([data-conversation-scroll]) .frame,
|
|
:global([data-conversation-scroll]) .root {
|
|
flex: 0 0 auto;
|
|
min-height: auto;
|
|
height: auto;
|
|
}
|
|
|
|
:global([data-conversation-scroll]) .scroll {
|
|
overflow: visible;
|
|
flex: 0 0 auto;
|
|
min-height: auto;
|
|
}
|
|
|
|
/* Message column: shared chat width (ConversationRoot --dsh-chat-content-width),
|
|
centered on the same axis as the input box (which caps at chat + 16px); the
|
|
scroller itself stays full-bleed. */
|
|
.column {
|
|
max-width: var(--dsh-chat-content-width);
|
|
width: 100%;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* The flow slot's display:contents anchor preserves layout but not selector ancestry.
|
|
`hidden="until-found"` retains a searchable zero-height box; hidden and empty
|
|
Seats do not contribute spacing. */
|
|
:is(.column, :global([data-slot="conversation.chat.flow"])) > :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)))
|
|
~ :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))),
|
|
.column:has(> .older) > :global([data-slot="conversation.chat.flow"])
|
|
> :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))) {
|
|
margin-top: var(--dsh-chat-flow-gap, 6px);
|
|
}
|
|
|
|
/* Process rows sit 6px apart; an Assistant response is separated from its
|
|
neighbours by 12px. */
|
|
.flowItem[data-chat-group-part="response"] {
|
|
--dsh-chat-flow-gap: 12px;
|
|
}
|
|
|
|
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-group-part="response"]:not([hidden]):not(:empty)
|
|
+ :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running) {
|
|
--dsh-chat-flow-gap: 12px;
|
|
}
|
|
|
|
/* Settled-flow identity boundary. It is neutral until it becomes the natural
|
|
measurement/mount unit for a virtualizer without changing the column gap. */
|
|
.flowItem {
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Bottom room: sized by the viewport owner while rows above the reader close,
|
|
given back only as later content grows into it. */
|
|
.turnSpacer {
|
|
height: 0;
|
|
}
|
|
|
|
/* While a Turn folds, spacing changes share the closing rows' timing. */
|
|
.column[data-chat-motion] > :not([data-chat-motion]),
|
|
.column[data-chat-motion] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
|
|
transition: margin-top 160ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.column[data-input-fold] > :not([data-chat-motion]),
|
|
.column[data-input-fold] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
|
|
transition: none;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.column[data-chat-motion] > :not([data-chat-motion]),
|
|
.column[data-chat-motion] > :global([data-slot="conversation.chat.flow"]) > :not([data-chat-motion]) {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
/* The Turn header has 16px clearance from its input and following content. */
|
|
.flowItem[data-chat-flow-kind="turn-process"],
|
|
.flowItem[data-turn-process-answer],
|
|
:is(.column, :global([data-slot="conversation.chat.flow"])) > [data-chat-flow-kind="turn-process"] + :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty))):not(.running) {
|
|
--dsh-chat-flow-gap: 16px;
|
|
}
|
|
|
|
/* Empty Seats, including retained empty slot anchors, preserve Turn lookup
|
|
without contributing height or the sibling gap. */
|
|
.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)) {
|
|
height: 0;
|
|
}
|
|
|
|
.callRow {
|
|
border-radius: var(--dsw-radius-sm);
|
|
}
|
|
|
|
/* Selection still sets data-selected for details linkage; no outline —
|
|
tool rows match Think chrome (no selected ring). */
|
|
|
|
.hint {
|
|
color: var(--dsw-alias-label-tertiary);
|
|
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
|
|
}
|
|
|
|
.running {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
--dsh-chat-flow-gap: 12px;
|
|
--dsw-alias-label-shimmer: var(--dsw-alias-label-deep-diving-shimmer);
|
|
color: var(--dsw-alias-label-deep-diving);
|
|
font-size: calc(var(--dsh-content-font-size, 14px) - 2px);
|
|
line-height: calc(22px + var(--dsh-content-font-delta, 0px));
|
|
}
|
|
|
|
/* The last visible row before the status must contain output, not an input.
|
|
Hidden and empty seats do not separate a user message from the status. */
|
|
:is(.column, :global([data-slot="conversation.chat.flow"])) > :nth-last-child(2 of :not([hidden]):not(.flowItem:is(:empty, :has(> [data-slot="conversation.chat.node"]:empty)))):is(
|
|
.flowItem:not([data-chat-flow-kind="user"]):not([data-chat-flow-kind="steering"]):not([data-chat-flow-kind="turn-trigger"]),
|
|
[data-chat-group-key]
|
|
) ~ .running > .runningDivider {
|
|
display: block;
|
|
}
|
|
|
|
.runningDivider {
|
|
display: none;
|
|
width: 100%;
|
|
height: 0.5px;
|
|
margin: 8px 0 10px;
|
|
background: color-mix(in srgb, var(--dsw-alias-border-l1) 75%, var(--dsw-alias-border-l2));
|
|
pointer-events: none;
|
|
}
|
|
|
|
.runningContent {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-width: 0;
|
|
gap: 6px;
|
|
}
|
|
|
|
.runningText {
|
|
min-width: 0;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
.runningIcon {
|
|
position: relative;
|
|
display: inline-flex;
|
|
flex: none;
|
|
width: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
height: calc(14px + var(--dsh-content-font-delta, 0px));
|
|
/* The fixed footprint contains the animation's layout and paint. */
|
|
contain: strict;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.runningWhaleAnimated {
|
|
display: none;
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
.runningWhaleStill {
|
|
display: initial;
|
|
}
|
|
|
|
@supports (mask-mode: alpha) and (mask-image: url('')) {
|
|
@media (prefers-reduced-motion: no-preference) and (forced-colors: none) {
|
|
.runningWhaleAnimated {
|
|
display: block;
|
|
background: currentColor;
|
|
mask: url('./[email protected]') center / 100% 100% no-repeat;
|
|
mask-mode: alpha;
|
|
/* Keep animated-mask repaints on an icon-sized composited layer. */
|
|
will-change: transform;
|
|
}
|
|
|
|
.runningWhaleStill {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.openError {
|
|
color: var(--dsw-alias-state-error-primary);
|
|
font-size: var(--dsh-content-font-size-secondary, 13px);
|
|
line-height: calc(18px + var(--dsh-content-font-delta-secondary, 0px));
|
|
}
|
|
|
|
.older {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
.older button {
|
|
border: none;
|
|
border-radius: var(--dsw-radius-sm);
|
|
padding: 4px 12px;
|
|
font-size: 12px;
|
|
color: var(--dsw-alias-label-secondary);
|
|
background: var(--dsw-alias-interactive-bg-hover-solid);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.older button:disabled {
|
|
cursor: default;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
/* Floating controls sit outside the clipped transcript. A standalone Chat
|
|
anchors them to its frame; the shared host supplies the sticky scrollport. */
|
|
.toBottomSlot {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 16px;
|
|
left: 0;
|
|
/* Above the sticky composer (z-index 7) so the control stays clickable and
|
|
visible over the input card. Exception: while an @/slash menu is open the
|
|
seat lifts to 9 (ui-conversation ConversationRoot) so the menu is never
|
|
covered by this control. */
|
|
z-index: 8;
|
|
height: 0;
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
padding-right: max(
|
|
calc(var(--dsh-composer-side-clearance) + 16px),
|
|
calc((100% - var(--dsh-chat-content-width)) / 2)
|
|
);
|
|
pointer-events: none;
|
|
}
|
|
|
|
:global([data-conversation-scroll]) .toBottomSlot {
|
|
position: sticky;
|
|
/* Clears the sticky composer stack (docks + input card + stats): the live
|
|
height rides --dsh-composer-height (ConversationRoot's seat observer) so
|
|
the control follows a growing textarea; the fallback covers the first
|
|
paint before the observer fires. */
|
|
bottom: calc(var(--dsh-composer-height, 152px) + 16px);
|
|
}
|
|
|
|
.toBottom {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 34px;
|
|
height: 34px;
|
|
margin-top: -34px;
|
|
padding: 0;
|
|
border: 0;
|
|
--dsw-elevation-stroke-color: var(--dsw-alias-border-l3);
|
|
border-radius: 100px;
|
|
corner-shape: round;
|
|
color: var(--dsw-alias-label-primary);
|
|
background: var(--dsw-alias-button-floating-fill);
|
|
box-shadow: var(--dsw-elevation-panel);
|
|
cursor: pointer;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.toBottom:hover {
|
|
background: var(--dsw-alias-button-floating-hover);
|
|
}
|
|
|
|
/* Host open-path refusal: same dialog family as the workspace folder error. */
|
|
.modalAction {
|
|
min-width: 72px;
|
|
}
|