mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
refactor(web): split global CSS by ownership (#2609)
* refactor(web): split global CSS by ownership * test(web): expand CSS imports in style checks * fix(web): keep privacy consent banner above modals
This commit is contained in:
parent
5d032aedec
commit
619087a6b4
35 changed files with 27878 additions and 27821 deletions
|
|
@ -143,6 +143,15 @@ root `pnpm tools-pr` script without a new explicit maintainer decision.
|
|||
- `AskUserQuestionCard` (in `ToolCard.tsx`) prefers the live `onAnswerToolUse(toolUseId, content)` route (POSTs to `/api/runs/:id/tool-result`) and falls back to the legacy `onSubmitForm(text)` path when the run has already terminated. Selected chips persist across reloads by parsing the stored `tool_result.content` back into the selections shape.
|
||||
- Tool group rendering uses `dedupeSnapshotToolRetries` to collapse identical `AskUserQuestion` retries (one card per unique input, keeping the latest tool_use_id) and `TodoWrite` snapshots (only the most recent call, since each call is a state replace).
|
||||
|
||||
## Web CSS ownership
|
||||
|
||||
- `apps/web/src/index.css` is an import-only cascade entrypoint. Do not add selectors or declarations there; add imports only when a truly global stylesheet is needed, and keep import order intentional.
|
||||
- Shared global styles belong in `apps/web/src/styles/`: design tokens, base/reset rules, primitives, app-shell layout, and legacy cross-component selectors that cannot safely be scoped yet. Keep domain-level global files grouped by owner (for example `styles/viewer/` and `styles/workspace/`) instead of adding more large files directly under `styles/`.
|
||||
- New component-owned UI styles should default to CSS Modules next to the component (`Component.module.css`) instead of expanding global stylesheets. This is preferred for isolated components, panels, menus, drawers, toolbars, cards, and form sections.
|
||||
- When touching an existing component with nearby global styles, prefer migrating that component's local selectors to a CSS Module as part of the change if it is small and testable. Do not mix a large mechanical move with behavior/styling changes in the same patch.
|
||||
- Keep global class names only for deliberate shared contracts: reusable primitives, theme hooks, third-party/content styling, cross-component layout, or selectors that rely on global cascade/specificity. Document any new global selector group with its owning feature.
|
||||
- CSS refactors must preserve cascade semantics. For mechanical splits, verify expanded import content/order matches the previous stylesheet; for CSS Module migrations, validate the affected UI path with `pnpm --filter @open-design/web typecheck` and a focused build/test or visual check when practical.
|
||||
|
||||
## i18n keys
|
||||
|
||||
- `apps/web/src/i18n/types.ts` is the typed `Dict`; every key must be defined in all 18 locale files under `apps/web/src/i18n/locales/*.ts` (`ar`, `de`, `en`, `es-ES`, `fa`, `fr`, `hu`, `id`, `ja`, `ko`, `pl`, `pt-BR`, `ru`, `th`, `tr`, `uk`, `zh-CN`, `zh-TW`). Add the key to `types.ts` first; missing translations produce a typecheck error.
|
||||
|
|
|
|||
|
|
@ -109,10 +109,13 @@ export function DesignSystemsSection({ cfg, setCfg }: Props) {
|
|||
useEffect(() => {
|
||||
if (!highlightedDesignSystemId) return;
|
||||
const raf = window.requestAnimationFrame(() => {
|
||||
cardRefs.current.get(highlightedDesignSystemId)?.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
const card = cardRefs.current.get(highlightedDesignSystemId);
|
||||
if (typeof card?.scrollIntoView === 'function') {
|
||||
card.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
});
|
||||
}
|
||||
});
|
||||
const timeout = window.setTimeout(() => {
|
||||
setHighlightedDesignSystemId((current) =>
|
||||
|
|
|
|||
27817
apps/web/src/index.css
27817
apps/web/src/index.css
File diff suppressed because it is too large
Load diff
23
apps/web/src/styles/base.css
Normal file
23
apps/web/src/styles/base.css
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
* { box-sizing: border-box; }
|
||||
|
||||
html, body, #root { height: 100%; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
color: var(--text);
|
||||
background: var(--bg-app);
|
||||
font-size: 13.5px;
|
||||
line-height: 1.5;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.od-loading-shell {
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-app);
|
||||
font: 500 13px/1.4 var(--sans);
|
||||
}
|
||||
|
||||
772
apps/web/src/styles/chat.css
Normal file
772
apps/web/src/styles/chat.css
Normal file
|
|
@ -0,0 +1,772 @@
|
|||
/* -------- Chat sticky header --------------------------------------- */
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-panel);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 4;
|
||||
height: 44px;
|
||||
}
|
||||
.chat-header-tabs { display: inline-flex; gap: 16px; flex: 1; }
|
||||
.chat-header-tab {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: 0;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
.chat-header-tab:hover { color: var(--text); background: transparent; border-color: transparent; }
|
||||
.chat-header-tab.active {
|
||||
color: var(--text);
|
||||
border-bottom-color: var(--text);
|
||||
}
|
||||
.chat-active-conversation {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.chat-active-conversation-title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.chat-active-conversation-rename {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-faint);
|
||||
border-radius: 5px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.chat-active-conversation-rename:hover {
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
}
|
||||
.chat-active-conversation-rename:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.chat-active-conversation-input {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 28px;
|
||||
padding: 3px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.chat-active-conversation-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--accent-soft);
|
||||
}
|
||||
.chat-header-actions { display: inline-flex; gap: 4px; align-items: center; margin-left: auto; }
|
||||
.chat-header-actions .icon-only {
|
||||
width: 28px; height: 28px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.chat-header-actions .icon-only:hover { background: var(--bg-subtle); color: var(--text); }
|
||||
|
||||
.chat-log {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* -------- Messages -------------------------------------------------- */
|
||||
.msg {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.msg.user {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
max-width: min(78%, 560px);
|
||||
}
|
||||
.msg .role {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
font-size: 12.5px;
|
||||
text-transform: none;
|
||||
color: var(--text-strong);
|
||||
margin-bottom: 4px;
|
||||
letter-spacing: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.msg-time {
|
||||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chat-day-separator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
.chat-day-separator::before,
|
||||
.chat-day-separator::after {
|
||||
content: '';
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
flex: 1;
|
||||
}
|
||||
.msg.user .role::before { content: ''; }
|
||||
.msg.user .role {
|
||||
justify-content: flex-end;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.msg.user .user-text {
|
||||
white-space: pre-wrap;
|
||||
color: #fff;
|
||||
background: var(--selected);
|
||||
border: 1px solid color-mix(in srgb, var(--selected) 88%, #000);
|
||||
border-radius: 14px 14px 4px 14px;
|
||||
padding: 8px 11px;
|
||||
line-height: 1.45;
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
.msg-plugin-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 4px 0 6px;
|
||||
padding: 4px 10px 4px 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
font-size: 11.5px;
|
||||
line-height: 1;
|
||||
color: var(--text-muted);
|
||||
max-width: 100%;
|
||||
}
|
||||
.msg-plugin-chip__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.msg-plugin-chip--design-system .msg-plugin-chip__dot {
|
||||
background: #d76445;
|
||||
}
|
||||
.msg-plugin-chip__label {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.msg-plugin-chip__kind {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
font-weight: 600;
|
||||
color: var(--text-faint);
|
||||
}
|
||||
.msg-plugin-chip__title {
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.msg-plugin-chip__version,
|
||||
.msg-plugin-chip__task {
|
||||
color: var(--text-faint);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.msg-plugin-chip__task {
|
||||
border-left: 1px solid var(--border);
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
/* Wrapper for user bubble + copy button */
|
||||
.msg.user .user-text-wrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
.msg.user .user-text-wrap .user-text {
|
||||
padding-inline-end: 28px;
|
||||
}
|
||||
.msg.user .user-status-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.user-status-card {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
max-width: min(320px, 100%);
|
||||
padding: 9px 11px;
|
||||
border-radius: 14px 14px 4px 14px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: var(--shadow-xs);
|
||||
color: var(--text);
|
||||
text-align: start;
|
||||
}
|
||||
.user-status-card__icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
}
|
||||
.user-status-card__copy {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
.user-status-card__copy strong {
|
||||
font-size: 12.5px;
|
||||
line-height: 1.2;
|
||||
font-weight: 650;
|
||||
}
|
||||
.user-status-card__copy span {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.35;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.msg.user .user-copy-btn {
|
||||
position: absolute;
|
||||
inset-block-start: 6px;
|
||||
inset-inline-end: 6px;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 120ms ease;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
}
|
||||
.msg.user .user-copy-btn:hover {
|
||||
color: #fff;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
.msg.user .user-text-wrap:hover .user-copy-btn,
|
||||
.msg.user .user-text-wrap:focus-within .user-copy-btn,
|
||||
.msg.user .user-copy-btn:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
.msg.user .user-copy-btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
@media (hover: none) {
|
||||
.msg.user .user-copy-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.msg.assistant .prose { margin-top: 4px; }
|
||||
.msg .artifact-badge {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
padding: 2px 8px;
|
||||
font-size: 11px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.msg.error {
|
||||
border: 1px solid var(--red-border);
|
||||
background: var(--red-bg);
|
||||
color: var(--red);
|
||||
padding: 10px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
.chat-error-text { min-width: 0; }
|
||||
.chat-error-retry {
|
||||
flex: 0 0 auto;
|
||||
border-color: var(--red-border);
|
||||
color: var(--red);
|
||||
padding: 4px 10px;
|
||||
}
|
||||
|
||||
/* -------- Composer -------------------------------------------------- */
|
||||
.composer {
|
||||
border-top: 1px solid var(--border);
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
background: var(--bg-panel);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
/* Elevation kicks in only when a pinned todo card is sitting directly
|
||||
* above the composer. Without the card the chat scroll already provides
|
||||
* its own visual edge; the shadow would just darken empty whitespace. */
|
||||
.chat-pinned-todo + .composer {
|
||||
box-shadow: 0 -10px 22px -10px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
/* Dark mode: the same alpha disappears against the dark panel, so push
|
||||
* it harder and let it spread a bit further. */
|
||||
[data-theme="dark"] .chat-pinned-todo + .composer {
|
||||
box-shadow: 0 -14px 28px -12px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) .chat-pinned-todo + .composer {
|
||||
box-shadow: 0 -14px 28px -12px rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
}
|
||||
.composer-shell {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-panel);
|
||||
box-shadow: var(--shadow-xs);
|
||||
padding: 8px 10px 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
.composer-shell:focus-within {
|
||||
border-color: var(--border-strong);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.composer.drag-active .composer-shell {
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-soft);
|
||||
}
|
||||
.composer textarea {
|
||||
min-height: 88px;
|
||||
max-height: min(184px, 34vh);
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 4px 4px;
|
||||
resize: none;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.composer textarea:focus { outline: none; box-shadow: none; }
|
||||
.composer-input-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.composer-textarea-layer {
|
||||
position: relative;
|
||||
min-height: 88px;
|
||||
}
|
||||
.composer-input-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
min-height: 88px;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
pointer-events: none;
|
||||
white-space: pre-wrap;
|
||||
overflow: hidden;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.composer-input-overlay-inner {
|
||||
min-height: 100%;
|
||||
padding: 4px;
|
||||
transform: translateY(calc(-1 * var(--composer-input-scroll, 0px)));
|
||||
}
|
||||
.composer-input-wrap.has-mention-overlay textarea {
|
||||
color: transparent;
|
||||
caret-color: var(--text);
|
||||
}
|
||||
.composer-input-wrap.has-mention-overlay textarea::selection {
|
||||
background: color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
color: transparent;
|
||||
}
|
||||
.composer-inline-mention {
|
||||
display: inline;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
box-decoration-break: clone;
|
||||
-webkit-box-decoration-break: clone;
|
||||
color: color-mix(in srgb, var(--accent) 62%, var(--text));
|
||||
font: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
line-height: inherit;
|
||||
letter-spacing: inherit;
|
||||
vertical-align: baseline;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: inherit;
|
||||
}
|
||||
.composer-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
.composer-row .icon-btn {
|
||||
width: 28px; height: 28px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.composer-row .icon-btn:hover:not(:disabled) { background: var(--bg-subtle); color: var(--text); }
|
||||
.composer-spacer { flex: 1; }
|
||||
.composer-import {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.composer-import:hover:not(:disabled) { background: var(--bg-subtle); color: var(--text); }
|
||||
.composer-research {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
.composer-research:hover:not(:disabled) { background: var(--bg-subtle); color: var(--text); }
|
||||
.composer-research.on {
|
||||
background: color-mix(in oklab, var(--accent) 12%, transparent);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.composer-research-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
}
|
||||
.composer-send {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 4px 14px;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
.composer-send:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
.composer-send.stop { background: var(--text); border-color: var(--text); color: var(--bg); }
|
||||
.composer-send.stop:hover { background: var(--text-strong); border-color: var(--text-strong); color: var(--bg); }
|
||||
.composer-hint {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.preview-draw-note-input::placeholder {
|
||||
color: rgba(255, 225, 210, 0.86);
|
||||
}
|
||||
|
||||
.preview-draw-note-input:focus {
|
||||
background: rgba(218, 97, 56, 0.26) !important;
|
||||
border-color: rgba(255, 184, 140, 0.95) !important;
|
||||
box-shadow: 0 0 0 3px rgba(218, 97, 56, 0.34), 0 0 18px rgba(218, 97, 56, 0.24) !important;
|
||||
}
|
||||
|
||||
/* -------- Staged attachments -------------------------------------- */
|
||||
.staged-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 4px 4px 0;
|
||||
}
|
||||
.staged-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px 4px 4px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
max-width: 220px;
|
||||
font-size: 11.5px;
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
.staged-chip img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.staged-preview-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
}
|
||||
.staged-preview-trigger:hover:not(:disabled) {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
.staged-preview-trigger img {
|
||||
flex: 0 0 28px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
.staged-preview-trigger .staged-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.staged-preview-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: rgba(17, 24, 39, 0.44);
|
||||
}
|
||||
.staged-preview-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: min(720px, calc(100vw - 48px));
|
||||
max-height: calc(100vh - 48px);
|
||||
padding: 12px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.staged-preview-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
.staged-preview-head span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.staged-preview-card > img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
max-height: min(70vh, 640px);
|
||||
object-fit: contain;
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
}
|
||||
.staged-comment {
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 4px 6px 4px 10px;
|
||||
}
|
||||
.user-attachment.staged-comment {
|
||||
max-width: 100%;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.staged-comment .staged-name {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.staged-comment .staged-name strong {
|
||||
color: var(--accent-strong);
|
||||
font-weight: 650;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.staged-comment .staged-name span {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
.staged-comment button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: var(--radius-pill);
|
||||
background: transparent;
|
||||
color: var(--text-faint);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
.staged-comment button:hover {
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text);
|
||||
}
|
||||
.staged-icon {
|
||||
width: 28px; height: 28px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.staged-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 140px;
|
||||
color: var(--text);
|
||||
}
|
||||
.staged-remove {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0 2px;
|
||||
color: var(--text-faint);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.staged-remove:hover { color: var(--red); background: var(--red-bg); }
|
||||
|
||||
.linked-dirs-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.linked-dir-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
max-width: 220px;
|
||||
}
|
||||
.linked-dir-chip svg { flex-shrink: 0; color: var(--text-muted); }
|
||||
.linked-dir-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.user-attachments {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.user-attachment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 10px 4px 4px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 11.5px;
|
||||
max-width: 240px;
|
||||
color: var(--text);
|
||||
cursor: default;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
font-size: 11.5px;
|
||||
}
|
||||
.user-attachment.openable { cursor: pointer; }
|
||||
.user-attachment.openable:hover {
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.user-attachment img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
object-fit: cover;
|
||||
border-radius: 6px;
|
||||
display: block;
|
||||
}
|
||||
.user-attachment .staged-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
284
apps/web/src/styles/primitives.css
Normal file
284
apps/web/src/styles/primitives.css
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
/* -------- Buttons --------------------------------------------------- */
|
||||
button {
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 6px 12px;
|
||||
cursor: pointer;
|
||||
transition: background 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
button:hover:not(:disabled) { background: var(--bg-subtle); border-color: var(--border-strong); }
|
||||
button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
button.primary {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
box-shadow: 0 1px 0 rgba(180, 90, 59, 0.18) inset, var(--shadow-xs);
|
||||
}
|
||||
button.primary:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
button.primary-ghost {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
button.primary-ghost:hover:not(:disabled) { background: var(--accent-tint); }
|
||||
|
||||
button.ghost {
|
||||
background: transparent;
|
||||
border-color: var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
button.ghost:hover:not(:disabled) { background: var(--bg-subtle); border-color: var(--border-strong); }
|
||||
/*
|
||||
* Transient success state for ghost buttons. Used by the Media
|
||||
* Providers "Reload from daemon" button after a successful reload —
|
||||
* the button briefly turns green with a check icon for ~2s then snaps
|
||||
* back to its idle treatment, replacing what used to be a permanent
|
||||
* success paragraph under the section header. Reusable on any
|
||||
* ghost button that wants the same "did it" pulse.
|
||||
*/
|
||||
button.ghost.is-success-flash {
|
||||
border-color: var(--green-border, color-mix(in srgb, #1f9d55 32%, var(--border)));
|
||||
background: var(--green-bg, color-mix(in srgb, #1f9d55 8%, transparent));
|
||||
color: var(--green, #137a3d);
|
||||
}
|
||||
button.ghost.is-success-flash:hover:not(:disabled) {
|
||||
background: var(--green-bg, color-mix(in srgb, #1f9d55 14%, transparent));
|
||||
}
|
||||
button.ghost.is-success-flash svg {
|
||||
color: currentColor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Visually-hidden but assistive-tech accessible. We use it to keep
|
||||
* a `role="status"` live-region for actions whose visible feedback
|
||||
* is too transient (e.g. a 2s button-label flash) for a screen
|
||||
* reader to catch reliably. Borrowed from Tailwind's `sr-only` so
|
||||
* it reads as the same primitive everyone already knows.
|
||||
*/
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button.subtle {
|
||||
background: var(--bg-subtle);
|
||||
border-color: transparent;
|
||||
color: var(--text);
|
||||
}
|
||||
button.subtle:hover:not(:disabled) { background: var(--bg-muted); }
|
||||
|
||||
button.icon-btn { padding: 6px 10px; font-size: 13px; }
|
||||
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
||||
|
||||
/* -------- Inputs ---------------------------------------------------- */
|
||||
input, textarea, select {
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 7px 10px;
|
||||
width: 100%;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
input::placeholder, textarea::placeholder { color: var(--text-faint); }
|
||||
input:focus, textarea:focus, select:focus {
|
||||
outline: none;
|
||||
border-color: var(--selected);
|
||||
box-shadow: 0 0 0 3px var(--selected-soft);
|
||||
}
|
||||
/* Entry sidebar form fields keep a quieter, neutral focus so the orange
|
||||
"Create" CTA stays the loudest thing in the panel. A low-opacity black
|
||||
halo reads as "focused" without re-introducing the global blue/accent
|
||||
ring. Using rgba directly because the Next CSS pipeline collapses
|
||||
`color-mix(in srgb, var(--text) 8%, transparent)` to a solid var(--text)
|
||||
ring (would render a 3px black band — too loud). */
|
||||
.entry-side input:focus,
|
||||
.entry-side textarea:focus,
|
||||
.entry-side select:focus {
|
||||
border-color: var(--text);
|
||||
box-shadow: 0 0 0 3px rgba(28, 27, 26, 0.08);
|
||||
}
|
||||
/* Native <select> on macOS uses its own intrinsic min-height that ends up
|
||||
shorter than <input> at the same `padding: 7px 10px` rule above, so any
|
||||
form that flex-aligns an input next to a select (e.g. the memory editor's
|
||||
Title + Type row) renders with mismatched heights. Stripping the native
|
||||
chrome lets the shared padding and inherited line-height compute the
|
||||
same box dimensions as the input, then we paint our own chevron via a
|
||||
background SVG. The chevron color is a per-theme override so it stays
|
||||
readable against the panel in both light and dark. */
|
||||
select {
|
||||
padding-right: 32px;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%2374716b' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3 5 6 8 9 5'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 12px 12px;
|
||||
}
|
||||
select::-ms-expand { display: none; }
|
||||
[data-theme='dark'] select {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%239a9690' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3 5 6 8 9 5'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 12px 12px;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html:not([data-theme]) select {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%239a9690' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='3 5 6 8 9 5'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 10px center;
|
||||
background-size: 12px 12px;
|
||||
}
|
||||
}
|
||||
textarea { resize: vertical; font-family: inherit; }
|
||||
|
||||
.od-select {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.od-select-trigger {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 36px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 10px;
|
||||
font: inherit;
|
||||
color: var(--text);
|
||||
text-align: left;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
||||
}
|
||||
.od-select-trigger:hover:not(:disabled),
|
||||
.od-select-trigger[aria-expanded='true'] {
|
||||
border-color: var(--border-strong);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.od-select-trigger:focus-visible,
|
||||
.od-select-trigger[aria-expanded='true'] {
|
||||
outline: none;
|
||||
border-color: var(--selected);
|
||||
box-shadow: 0 0 0 3px var(--selected-soft);
|
||||
}
|
||||
.od-select-trigger:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.od-select-trigger svg {
|
||||
color: var(--text-muted);
|
||||
transition: transform 120ms ease;
|
||||
}
|
||||
.od-select-trigger[aria-expanded='true'] svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.od-select-value,
|
||||
.od-select-option-label {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.od-select-menu {
|
||||
z-index: 9000;
|
||||
padding: 4px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
max-width: calc(100vw - 24px);
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
.od-select-menu.portal {
|
||||
position: fixed;
|
||||
}
|
||||
.od-select-menu.inline {
|
||||
position: absolute;
|
||||
top: calc(100% + 4px);
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-height: min(280px, 48vh);
|
||||
}
|
||||
.od-select-option {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 30px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 16px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
color: var(--text);
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
font: inherit;
|
||||
font-size: 12.5px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.od-select-option:hover:not(:disabled),
|
||||
.od-select-option.active:not(:disabled) {
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.od-select-option.selected {
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
}
|
||||
.od-select-option:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.od-select-option-check {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
color: var(--selected);
|
||||
opacity: 0;
|
||||
}
|
||||
.od-select-option.selected .od-select-option-check {
|
||||
opacity: 1;
|
||||
}
|
||||
.od-select-group + .od-select-group {
|
||||
margin-top: 4px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
}
|
||||
.od-select-group-label {
|
||||
padding: 6px 8px 4px;
|
||||
color: var(--text-faint);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--mono);
|
||||
background: var(--bg-subtle);
|
||||
padding: 1px 5px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.92em;
|
||||
color: var(--text);
|
||||
}
|
||||
1254
apps/web/src/styles/shell.css
Normal file
1254
apps/web/src/styles/shell.css
Normal file
File diff suppressed because it is too large
Load diff
164
apps/web/src/styles/tokens.css
Normal file
164
apps/web/src/styles/tokens.css
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
/* ============================================================
|
||||
Open Design — neutral product workspace
|
||||
============================================================ */
|
||||
:root {
|
||||
/* Surface palette — neutral app chrome that does not bias generated artifacts.
|
||||
Keep warm/brand colors out of preview backgrounds; generated product UI
|
||||
must choose its own palette through the active skill/design brief. */
|
||||
--bg: #faf9f7;
|
||||
--bg-app: #faf9f7;
|
||||
--bg-panel: #ffffff;
|
||||
--bg-subtle: #eef1f5;
|
||||
--bg-muted: #e4e8ef;
|
||||
--bg-elevated: #ffffff;
|
||||
--border: #e1e5eb;
|
||||
--border-strong: #c9d0da;
|
||||
--border-soft: #edf0f4;
|
||||
|
||||
--text: #1a1916;
|
||||
--text-strong: #0d0c0a;
|
||||
--text-muted: #74716b;
|
||||
--text-soft: #989590;
|
||||
--text-faint: #b3b0a8;
|
||||
|
||||
/* Accent — Open Design action color for app chrome only. */
|
||||
--accent: #c96442;
|
||||
--accent-strong: #b45a3b;
|
||||
--accent-soft: #f5d8cb;
|
||||
--accent-tint: #fbeee5;
|
||||
--accent-hover: #b45a3b;
|
||||
|
||||
/* Semantic accent tints used by tool / status pills. */
|
||||
--green: #1f7a3a;
|
||||
--green-bg: #e8f7ee;
|
||||
--green-border: #c6ead2;
|
||||
--blue: #2348b8;
|
||||
--blue-bg: #e8efff;
|
||||
--blue-border: #c8d6ff;
|
||||
--purple: #6c3aa6;
|
||||
--purple-bg: #f3ecf9;
|
||||
--purple-border: #e4d4f1;
|
||||
--red: #9c2a25;
|
||||
--red-bg: #fdecea;
|
||||
--red-border: #f5c6c2;
|
||||
--amber: #b26200;
|
||||
--amber-bg: #fff3e0;
|
||||
|
||||
--shadow-xs: 0 1px 0 rgba(28, 27, 26, 0.04);
|
||||
--shadow-sm: 0 1px 2px rgba(28, 27, 26, 0.05), 0 1px 3px rgba(28, 27, 26, 0.04);
|
||||
--shadow-md: 0 6px 24px rgba(28, 27, 26, 0.07), 0 2px 6px rgba(28, 27, 26, 0.04);
|
||||
--shadow-lg: 0 24px 60px rgba(28, 27, 26, 0.16), 0 8px 16px rgba(28, 27, 26, 0.07);
|
||||
|
||||
--radius-sm: 8px;
|
||||
--radius: 12px;
|
||||
--radius-lg: 16px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
/* "Active option" indicator color, intentionally separate from --accent
|
||||
(brand orange used by primary CTAs like Create / Save). Use --selected
|
||||
for "this option is the one currently picked" affordances — selected
|
||||
fidelity card, focused input ring, active filter pill — so a primary
|
||||
CTA and a selected state can coexist on the same screen without
|
||||
competing. --selected-soft is the 16% tint for outer rings / fills. */
|
||||
--selected: #2563eb;
|
||||
--selected-soft: rgba(37, 99, 235, 0.16);
|
||||
|
||||
--serif: 'Source Serif Pro', 'Source Serif 4', 'Iowan Old Style', 'Apple Garamond', Georgia, 'Times New Roman', serif;
|
||||
--sans: -apple-system, BlinkMacSystemFont, 'Inter', 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Dark theme variables — shared between explicit [data-theme="dark"] and the
|
||||
OS-level prefers-color-scheme media query (system mode = no data-theme attr). */
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--bg: #1a1917;
|
||||
--bg-app: #1a1917;
|
||||
--bg-panel: #222120;
|
||||
--bg-subtle: #272523;
|
||||
--bg-muted: #2e2c29;
|
||||
--bg-elevated: #2a2825;
|
||||
--border: #333128;
|
||||
--border-strong: #46433c;
|
||||
--border-soft: #2a2825;
|
||||
|
||||
--text: #e8e4dc;
|
||||
--text-strong: #f2ede4;
|
||||
--text-muted: #9a9690;
|
||||
--text-soft: #6e6b65;
|
||||
--text-faint: #4e4b46;
|
||||
|
||||
--accent: #d97a56;
|
||||
--accent-strong: #e8896a;
|
||||
--accent-soft: #3d2318;
|
||||
--accent-tint: #2e1a12;
|
||||
--accent-hover: #e8896a;
|
||||
|
||||
--green: #4caf72;
|
||||
--green-bg: #0f2a18;
|
||||
--green-border: #1a4028;
|
||||
--blue: #6b8fe8;
|
||||
--blue-bg: #0f1a38;
|
||||
--blue-border: #1a2c58;
|
||||
--purple: #a87dd4;
|
||||
--purple-bg: #1e1030;
|
||||
--purple-border: #321a50;
|
||||
--red: #e06b65;
|
||||
--red-bg: #2a0e0c;
|
||||
--red-border: #451714;
|
||||
--amber: #e09a40;
|
||||
--amber-bg: #2a1a04;
|
||||
|
||||
--shadow-xs: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 6px 24px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||
--shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* System mode: follow OS preference when no explicit data-theme is set. */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html:not([data-theme]) {
|
||||
color-scheme: dark;
|
||||
--bg: #1a1917;
|
||||
--bg-app: #1a1917;
|
||||
--bg-panel: #222120;
|
||||
--bg-subtle: #272523;
|
||||
--bg-muted: #2e2c29;
|
||||
--bg-elevated: #2a2825;
|
||||
--border: #333128;
|
||||
--border-strong: #46433c;
|
||||
--border-soft: #2a2825;
|
||||
|
||||
--text: #e8e4dc;
|
||||
--text-strong: #f2ede4;
|
||||
--text-muted: #9a9690;
|
||||
--text-soft: #6e6b65;
|
||||
--text-faint: #4e4b46;
|
||||
|
||||
--accent: #d97a56;
|
||||
--accent-strong: #e8896a;
|
||||
--accent-soft: #3d2318;
|
||||
--accent-tint: #2e1a12;
|
||||
--accent-hover: #e8896a;
|
||||
|
||||
--green: #4caf72;
|
||||
--green-bg: #0f2a18;
|
||||
--green-border: #1a4028;
|
||||
--blue: #6b8fe8;
|
||||
--blue-bg: #0f1a38;
|
||||
--blue-border: #1a2c58;
|
||||
--purple: #a87dd4;
|
||||
--purple-bg: #1e1030;
|
||||
--purple-border: #321a50;
|
||||
--red: #e06b65;
|
||||
--red-bg: #2a0e0c;
|
||||
--red-border: #451714;
|
||||
--amber: #e09a40;
|
||||
--amber-bg: #2a1a04;
|
||||
|
||||
--shadow-xs: 0 1px 0 rgba(0, 0, 0, 0.2);
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
--shadow-md: 0 6px 24px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||
--shadow-lg: 0 24px 60px rgba(0, 0, 0, 0.6), 0 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
767
apps/web/src/styles/viewer/code.css
Normal file
767
apps/web/src/styles/viewer/code.css
Normal file
|
|
@ -0,0 +1,767 @@
|
|||
/* Code viewer with line numbers */
|
||||
.code-viewer {
|
||||
background: var(--bg-panel);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
min-height: 100%;
|
||||
}
|
||||
.code-viewer .gutter {
|
||||
background: var(--bg);
|
||||
color: var(--text-faint);
|
||||
text-align: right;
|
||||
padding: 16px 12px 16px 16px;
|
||||
user-select: none;
|
||||
border-right: 1px solid var(--border-soft);
|
||||
white-space: pre;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.code-viewer .lines {
|
||||
padding: 16px 16px 16px 18px;
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.viewer-empty {
|
||||
padding: 48px 24px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
/* Compact horizontal variant for the upload-failure banner: the message
|
||||
sits inline next to a dismiss button so the user can clear the stale
|
||||
notice without changing tabs. Issue #786. */
|
||||
.viewer-empty.viewer-empty-dismissible {
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.document-preview {
|
||||
max-width: 860px;
|
||||
margin: 0 auto;
|
||||
padding: 32px 40px 56px;
|
||||
color: var(--text);
|
||||
}
|
||||
.document-preview h2 {
|
||||
margin: 0 0 24px;
|
||||
font-size: 20px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.document-preview section {
|
||||
border-top: 1px solid var(--border-soft);
|
||||
padding-top: 18px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.document-preview h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
.document-preview p {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.markdown-rendered {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 28px 40px;
|
||||
color: var(--text);
|
||||
line-height: 1.65;
|
||||
white-space: normal;
|
||||
}
|
||||
.markdown-status {
|
||||
margin: 12px auto 0;
|
||||
max-width: 900px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-panel);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
.markdown-status-error {
|
||||
border-color: color-mix(in oklab, var(--danger, #d04b4b) 45%, var(--border-soft));
|
||||
color: var(--danger, #d04b4b);
|
||||
}
|
||||
.markdown-rendered h1,
|
||||
.markdown-rendered h2,
|
||||
.markdown-rendered h3,
|
||||
.markdown-rendered h4,
|
||||
.markdown-rendered h5,
|
||||
.markdown-rendered h6 {
|
||||
margin: 20px 0 10px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
.markdown-rendered p { margin: 10px 0; }
|
||||
.markdown-rendered ul,
|
||||
.markdown-rendered ol {
|
||||
margin: 10px 0;
|
||||
padding-left: 24px;
|
||||
}
|
||||
.markdown-rendered blockquote {
|
||||
margin: 12px 0;
|
||||
padding: 8px 12px;
|
||||
border-left: 3px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.markdown-code-block {
|
||||
position: relative;
|
||||
}
|
||||
.markdown-code-copy {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
z-index: 1;
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--bg-panel) 92%, black 8%);
|
||||
color: var(--text-muted);
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
padding: 7px 10px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transform: translateY(-2px);
|
||||
transition:
|
||||
opacity 120ms ease,
|
||||
transform 120ms ease,
|
||||
color 120ms ease,
|
||||
border-color 120ms ease,
|
||||
background 120ms ease;
|
||||
}
|
||||
.markdown-code-block:hover .markdown-code-copy,
|
||||
.markdown-code-block:focus-within .markdown-code-copy {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
@media (hover: none) {
|
||||
.markdown-code-copy {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.markdown-code-copy:hover,
|
||||
.markdown-code-copy:focus-visible {
|
||||
color: var(--text);
|
||||
border-color: var(--border);
|
||||
background: var(--bg-elevated, var(--bg));
|
||||
}
|
||||
.markdown-code-toast {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
right: 82px;
|
||||
z-index: 1;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in oklab, var(--accent) 18%, var(--bg-panel));
|
||||
color: var(--text);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
padding: 7px 10px;
|
||||
box-shadow: 0 10px 26px color-mix(in oklab, var(--accent) 18%, transparent);
|
||||
}
|
||||
.markdown-rendered pre {
|
||||
margin: 12px 0;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 8px;
|
||||
padding: 40px 12px 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
.markdown-rendered code {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
.markdown-rendered a { color: var(--accent); }
|
||||
.image-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
.image-body img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
background: white;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.sketch-preview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
.sketch-preview svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
background: white;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.sketch-preview.loading {
|
||||
background:
|
||||
radial-gradient(circle at 8px 8px, #d7d4ce 1px, transparent 1px),
|
||||
#f7f5f1;
|
||||
background-size: 16px 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.sketch-preview-empty-mark path {
|
||||
stroke: #bfbcb6;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
fill: none;
|
||||
}
|
||||
|
||||
/* Sketch editor */
|
||||
.sketch-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
.sketch-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-panel);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.sketch-tool {
|
||||
padding: 6px 10px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
min-width: 32px;
|
||||
}
|
||||
.sketch-tool:hover { background: var(--bg-subtle); }
|
||||
.sketch-tool.active { background: var(--accent); color: white; border-color: var(--accent); }
|
||||
.sketch-divider { width: 1px; height: 20px; background: var(--border); margin: 0 4px; }
|
||||
.sketch-color { width: 32px; height: 28px; padding: 0; border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }
|
||||
.sketch-size { width: 80px; background: transparent; border: none; }
|
||||
.sketch-spacer { flex: 1; }
|
||||
.sketch-canvas-wrap { flex: 1; min-height: 0; position: relative; background: var(--bg); }
|
||||
.sketch-canvas-wrap canvas { display: block; cursor: crosshair; }
|
||||
|
||||
/* ===========================================================
|
||||
Streaming chat: assistant message header, prose, thinking,
|
||||
tool cards, status pills, grouped action card.
|
||||
=========================================================== */
|
||||
.chat-empty-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
padding: 24px 8px;
|
||||
min-height: 100%;
|
||||
}
|
||||
.chat-empty {
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 6px;
|
||||
max-width: 44ch;
|
||||
}
|
||||
.chat-empty-title { font-weight: 600; color: var(--text-strong); font-size: 15px; }
|
||||
.chat-empty-hint { line-height: 1.6; }
|
||||
|
||||
.chat-examples {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.chat-example {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 14px 16px;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-xs);
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
transform 160ms ease,
|
||||
border-color 160ms ease,
|
||||
box-shadow 160ms ease,
|
||||
background-color 160ms ease;
|
||||
opacity: 0;
|
||||
animation: chat-example-in 380ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
.chat-example::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--accent-tint) 0%,
|
||||
transparent 55%
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 200ms ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
.chat-example:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--accent);
|
||||
box-shadow: var(--shadow-md);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.chat-example:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
.chat-example:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
.chat-example:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.chat-example-icon {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04), inset 0 -1px 0 rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.chat-example-body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.chat-example-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.chat-example-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.chat-example-tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
background: var(--accent-tint);
|
||||
border: 1px solid var(--accent-soft);
|
||||
padding: 1px 7px;
|
||||
border-radius: var(--radius-pill);
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.chat-example-prompt {
|
||||
color: var(--text-muted);
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.chat-example-cta {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
transition: background-color 160ms ease, color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
.chat-example:hover .chat-example-cta {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
transform: translateX(2px);
|
||||
}
|
||||
|
||||
/* Connect-your-repo CTA in the empty chat state. Shares the example cards'
|
||||
sizing and accent system but stands apart with a tinted fill so it reads as
|
||||
guidance rather than another starter prompt. */
|
||||
.chat-connect-repo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
padding: 14px 16px;
|
||||
background: var(--accent-tint);
|
||||
border: 1px solid var(--accent-soft);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-xs);
|
||||
opacity: 0;
|
||||
animation: chat-example-in 380ms cubic-bezier(0.22, 1, 0.36, 1) 220ms forwards;
|
||||
}
|
||||
.chat-connect-repo-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.chat-connect-repo-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.chat-connect-repo-title {
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
font-size: 13.5px;
|
||||
}
|
||||
.chat-connect-repo-text {
|
||||
color: var(--text-muted);
|
||||
font-size: 12.5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.chat-connect-repo button {
|
||||
flex-shrink: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes chat-example-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.assistant-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.assistant-header .dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-muted);
|
||||
}
|
||||
.assistant-header .dot[data-active="true"] {
|
||||
background: var(--accent);
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
.assistant-label { font-weight: 600; color: var(--text-strong); font-size: 12.5px; }
|
||||
.assistant-stats { font-variant-numeric: tabular-nums; margin-left: auto; }
|
||||
|
||||
.assistant-flow {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.prose-block { line-height: 1.6; color: var(--text); }
|
||||
.prose-block .md-p { margin: 0; }
|
||||
.prose-block .md-p + .md-p { margin-top: 8px; }
|
||||
.prose-block .md-h { margin: 10px 0 4px; line-height: 1.3; font-weight: 600; }
|
||||
.prose-block .md-h1 { font-size: 18px; }
|
||||
.prose-block .md-h2 { font-size: 16px; }
|
||||
.prose-block .md-h3 { font-size: 14px; }
|
||||
.prose-block .md-h4 { font-size: 13px; }
|
||||
.prose-block .md-ul, .prose-block .md-ol { margin: 4px 0; padding-left: 20px; }
|
||||
.prose-block .md-ul li, .prose-block .md-ol li { margin: 2px 0; }
|
||||
.prose-block .md-inline-code {
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 1px 5px;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.92em;
|
||||
}
|
||||
.prose-block .md-code {
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
margin: 6px 0;
|
||||
overflow-x: auto;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.prose-block .md-code code { font-family: var(--mono); }
|
||||
.prose-block .md-link {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.prose-block .md-link-bare {
|
||||
/* Long bare URLs (OAuth flows etc.) need to wrap mid-string so they
|
||||
don't escape the chat column. break-word is enough for most agents,
|
||||
but `anywhere` covers query strings with no spaces. */
|
||||
word-break: break-all;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.prose-block .md-hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--border);
|
||||
margin: 10px 0;
|
||||
}
|
||||
/* GFM pipe tables. Rules are intentionally unscoped so both the chat path
|
||||
(`.prose-block`) and the artifact preview (`.markdown-rendered`) get
|
||||
the same treatment. Horizontal scroll lives on the wrapper so the
|
||||
inner <table> can keep `display: table; width: 100%` and distribute
|
||||
column widths across the container instead of collapsing to content. */
|
||||
.md-table-wrap {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
margin: 12px 0;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.md-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.md-table th,
|
||||
.md-table td {
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.md-table th + th,
|
||||
.md-table td + td {
|
||||
border-left: 1px solid var(--border);
|
||||
}
|
||||
.md-table thead th {
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--border);
|
||||
}
|
||||
.md-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.md-table tbody tr:nth-child(even) td {
|
||||
background: color-mix(in oklab, var(--bg-panel) 40%, transparent);
|
||||
}
|
||||
.op-waiting {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Thinking blocks */
|
||||
.thinking-block {
|
||||
background: rgba(108, 58, 166, 0.04);
|
||||
border: 1px solid rgba(108, 58, 166, 0.16);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.thinking-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 7px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
.thinking-toggle:hover { background: rgba(108, 58, 166, 0.05); border-color: transparent; }
|
||||
.thinking-icon { color: var(--purple); }
|
||||
.thinking-label { font-weight: 500; }
|
||||
.thinking-preview {
|
||||
flex: 1;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.thinking-chev { color: var(--text-muted); font-size: 10px; }
|
||||
.thinking-body {
|
||||
margin: 0;
|
||||
padding: 0 14px 12px 14px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
color: var(--text-muted);
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* Status pills */
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
max-width: 100%;
|
||||
padding: 3px 12px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.status-label { letter-spacing: 0.02em; }
|
||||
.status-detail {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
color: var(--text);
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
/* Grouped tool / action card — the collapsible pill from screenshot 9 */
|
||||
.action-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-panel);
|
||||
overflow: hidden;
|
||||
}
|
||||
.action-card-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
font-size: 12.5px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.action-card-toggle:hover { background: var(--bg-subtle); border-color: transparent; }
|
||||
.action-card-toggle .ico {
|
||||
width: 20px; height: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-family: var(--mono);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.action-card-toggle .summary { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.action-card-toggle .summary strong { font-weight: 500; }
|
||||
.action-card-toggle .chev { color: var(--text-faint); font-size: 10px; flex-shrink: 0; }
|
||||
.action-card-toggle.running .ico { animation: pulse 1.6s ease-in-out infinite; background: var(--purple-bg); color: var(--purple); }
|
||||
.action-card-body {
|
||||
padding: 0 12px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
}
|
||||
.action-card-body > .op-card { border-color: transparent; box-shadow: none; padding: 4px 0; }
|
||||
.action-card-body > .op-card .op-card-head { padding: 6px 0; }
|
||||
|
||||
/* Tool / operation cards — single, ungrouped */
|
||||
.op-card {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-panel);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-xs);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
.op-card-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12.5px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.op-icon {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-subtle);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-family: var(--mono);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.op-icon-write { background: var(--green-bg); color: var(--green); }
|
||||
.op-icon-edit { background: var(--amber-bg); color: var(--amber); }
|
||||
.op-icon-read { background: var(--blue-bg); color: var(--blue); }
|
||||
.op-title { font-weight: 500; }
|
||||
2141
apps/web/src/styles/viewer/composio.css
Normal file
2141
apps/web/src/styles/viewer/composio.css
Normal file
File diff suppressed because it is too large
Load diff
1787
apps/web/src/styles/viewer/core.css
Normal file
1787
apps/web/src/styles/viewer/core.css
Normal file
File diff suppressed because it is too large
Load diff
2223
apps/web/src/styles/viewer/library.css
Normal file
2223
apps/web/src/styles/viewer/library.css
Normal file
File diff suppressed because it is too large
Load diff
1305
apps/web/src/styles/viewer/memory.css
Normal file
1305
apps/web/src/styles/viewer/memory.css
Normal file
File diff suppressed because it is too large
Load diff
1062
apps/web/src/styles/viewer/pets.css
Normal file
1062
apps/web/src/styles/viewer/pets.css
Normal file
File diff suppressed because it is too large
Load diff
334
apps/web/src/styles/viewer/plugin-inputs.css
Normal file
334
apps/web/src/styles/viewer/plugin-inputs.css
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
/* ============================================================
|
||||
Note: the entry-view home redesign (left rail, centered hero,
|
||||
recent projects strip, plugins-home section, new-project modal)
|
||||
lives in `src/styles/home/`. Imported from `app/layout.tsx`
|
||||
alongside this file so upstream `index.css` rebases stay narrow.
|
||||
============================================================ */
|
||||
.settings-skills .skills-row-summary-btn:hover .skills-row-chevron {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.settings-skills .skills-row-expanded .skills-row-chevron {
|
||||
transform: rotate(180deg);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.settings-skills .skills-row-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.settings-skills .skills-row-actions .icon-btn {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
color: var(--text-faint);
|
||||
cursor: pointer;
|
||||
transition: background-color 120ms ease, border-color 120ms ease,
|
||||
color 120ms ease;
|
||||
}
|
||||
.settings-skills .skills-row-actions .icon-btn:hover {
|
||||
background: var(--bg-panel);
|
||||
border-color: var(--border);
|
||||
color: var(--text);
|
||||
}
|
||||
.settings-skills .skills-row-enable {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.settings-skills .skills-delete-confirm {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.settings-skills .skills-delete-confirm .btn {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.settings-skills .skills-row-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 4px 14px 14px;
|
||||
border-top: 1px dashed var(--border);
|
||||
margin: 0 4px;
|
||||
}
|
||||
.settings-skills .skills-row-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.settings-skills .skills-row-section h5 {
|
||||
margin: 0;
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.07em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
font-weight: 600;
|
||||
}
|
||||
.settings-skills .skills-row-section .library-preview-body {
|
||||
margin: 0;
|
||||
max-height: 320px;
|
||||
overflow: auto;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.settings-skills .skills-file-tree {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
overflow-y: auto;
|
||||
max-height: 240px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
.settings-skills .skills-file-entry {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 3px 0;
|
||||
}
|
||||
.settings-skills .skills-file-entry-directory {
|
||||
color: var(--text);
|
||||
font-weight: 500;
|
||||
}
|
||||
.settings-skills .skills-file-size {
|
||||
margin-left: auto;
|
||||
color: var(--text-faint);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.settings-skills > .skills-draft {
|
||||
margin: 0;
|
||||
}
|
||||
.settings-skills .skills-row > .skills-draft {
|
||||
border: none;
|
||||
border-top: 1px dashed var(--border);
|
||||
border-radius: 0 0 10px 10px;
|
||||
margin: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.settings-skills .skills-draft .skills-draft-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.settings-skills .skills-draft .skills-draft-head h4 {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.settings-skills .skills-draft .skills-draft-sub {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
font-family: var(--mono);
|
||||
}
|
||||
|
||||
.mcp-json-helper {
|
||||
margin-top: 10px;
|
||||
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
|
||||
background: var(--bg-subtle);
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mcp-json-helper-toggle {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
|
||||
border: none;
|
||||
border-radius: calc(var(--radius) - 2px);
|
||||
|
||||
background: transparent;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
|
||||
transition:
|
||||
color 120ms ease,
|
||||
background 120ms ease,
|
||||
box-shadow 120ms ease;
|
||||
}
|
||||
|
||||
.mcp-json-helper-toggle:hover {
|
||||
color: var(--text);
|
||||
background: transparent;
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-soft) 30%, transparent);
|
||||
}
|
||||
|
||||
.mcp-json-helper.is-open .mcp-json-helper-toggle {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: inset 0 -1px 0 var(--border);
|
||||
}
|
||||
|
||||
.mcp-json-helper.is-open .mcp-json-helper-toggle:hover {
|
||||
box-shadow: inset 0 -1px 0 var(--border);
|
||||
}
|
||||
.mcp-json-helper-toggle span:first-child {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mcp-json-helper-toggle-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.mcp-json-helper-example {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mcp-json-helper-example-head {
|
||||
padding: 10px 12px 0;
|
||||
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.mcp-json-helper-code {
|
||||
margin: 0 12px 12px;
|
||||
|
||||
padding: 14px;
|
||||
|
||||
border: 1px solid var(--border);
|
||||
border-radius: calc(var(--radius) - 4px);
|
||||
|
||||
background:
|
||||
linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--bg) 92%, black) 0%,
|
||||
color-mix(in srgb, var(--bg) 86%, black) 100%
|
||||
);
|
||||
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
|
||||
color: var(--text);
|
||||
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.mcp-json-helper-code code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.json-key {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.json-string {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.json-punctuation {
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.mcp-json-helper-conversion {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
padding: 12px;
|
||||
|
||||
border-top: 1px solid var(--border);
|
||||
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.mcp-json-helper-conversion div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.mcp-json-helper-conversion strong {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.mcp-json-helper-conversion code {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.mcp-json-helper-toggle-content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mcp-json-helper-eye {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
border-radius: 999px;
|
||||
|
||||
background: var(--accent-tint);
|
||||
|
||||
color: var(--accent);
|
||||
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mcp-json-helper-toggle-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
|
||||
color: inherit;
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
432
apps/web/src/styles/viewer/plugin-rail.css
Normal file
432
apps/web/src/styles/viewer/plugin-rail.css
Normal file
|
|
@ -0,0 +1,432 @@
|
|||
/* ==================================================================
|
||||
Plugin detail — premium chrome
|
||||
------------------------------------------------------------------
|
||||
Shared visual language for the four detail variants:
|
||||
- Smooth scale-in entry (used by every detail backdrop + modal)
|
||||
- Trust-tinted byline strip (PluginByline) reused across surfaces
|
||||
- Share popover (PluginShareMenu) reused across surfaces
|
||||
- Cinematic media detail layout
|
||||
The goal: detail modals feel like curated content viewers users
|
||||
want to share, not like internal admin inspectors.
|
||||
================================================================== */
|
||||
|
||||
@keyframes plugin-detail-fade {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes plugin-detail-rise {
|
||||
from { opacity: 0; transform: translateY(12px) scale(0.985); }
|
||||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||||
}
|
||||
|
||||
.plugin-details-modal-backdrop,
|
||||
.ds-modal-backdrop,
|
||||
.prompt-template-modal-backdrop {
|
||||
animation: plugin-detail-fade 160ms ease-out;
|
||||
}
|
||||
.plugin-details-modal,
|
||||
.ds-modal,
|
||||
.prompt-template-modal {
|
||||
animation: plugin-detail-rise 220ms cubic-bezier(0.16, 0.84, 0.44, 1);
|
||||
}
|
||||
|
||||
.plugin-details-modal__head-actions {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
/* PluginShareMenu — popover trigger that doubles as a regular ghost
|
||||
pill in the PreviewModal action row, or a softer brand chip when
|
||||
it stands alone next to the close button on a detail modal. */
|
||||
.plugin-share-menu {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
.plugin-share-trigger {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 999px;
|
||||
padding: 5px 11px;
|
||||
cursor: pointer;
|
||||
transition: background-color 120ms ease, color 120ms ease,
|
||||
border-color 120ms ease;
|
||||
}
|
||||
.plugin-share-trigger--solo {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
}
|
||||
.plugin-share-trigger--solo:hover,
|
||||
.plugin-share-trigger--solo[aria-expanded='true'] {
|
||||
background: var(--accent-tint);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.plugin-share-popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
min-width: 220px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
animation: plugin-detail-rise 140ms ease-out;
|
||||
}
|
||||
.plugin-share-popover__group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
.plugin-share-popover__divider {
|
||||
height: 1px;
|
||||
margin: 4px 4px;
|
||||
background: var(--border);
|
||||
opacity: 0.6;
|
||||
}
|
||||
.plugin-share-item {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-radius: 6px;
|
||||
padding: 7px 9px;
|
||||
font-size: 12.5px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
}
|
||||
.plugin-share-item:hover,
|
||||
.plugin-share-item:focus-visible {
|
||||
background: var(--bg-subtle);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* PluginByline — author avatar + name + trust tier + version chip,
|
||||
shared by media + scenario variants, lives just under the modal
|
||||
title bar. The compact variant collapses the secondary link row
|
||||
when used inside dark/gradient headers. */
|
||||
.plugin-byline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bg-panel);
|
||||
}
|
||||
.plugin-byline--compact {
|
||||
padding: 8px 16px;
|
||||
border-bottom: none;
|
||||
background: transparent;
|
||||
}
|
||||
.plugin-byline__avatar {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 999px;
|
||||
background: var(--bg-subtle);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11.5px;
|
||||
font-weight: 700;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
}
|
||||
.plugin-byline__avatar--fallback {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--accent-tint) 0%,
|
||||
var(--bg-subtle) 100%
|
||||
);
|
||||
color: var(--accent);
|
||||
}
|
||||
.plugin-byline__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
.plugin-byline__primary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.plugin-byline__by {
|
||||
font-size: 11.5px;
|
||||
color: var(--text-faint);
|
||||
text-transform: lowercase;
|
||||
}
|
||||
.plugin-byline__name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
.plugin-byline__version {
|
||||
font-size: 11px;
|
||||
color: var(--text-faint);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.plugin-byline__secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.plugin-byline__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
transition: color 120ms ease;
|
||||
}
|
||||
.plugin-byline__link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* Plugins section composite — wraps InlinePluginsRail + ContextChipStrip +
|
||||
PluginInputsForm. Mounted above ChatComposer (strip variant). The
|
||||
NewProjectPanel mount has been removed: that flow now binds the
|
||||
default scenario plugin transparently at create time, so the wide
|
||||
variant has no remaining host. */
|
||||
.plugins-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.plugins-section__active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* Strip rail — single-row horizontal scroller, intended for the
|
||||
ChatComposer mount where vertical space is precious. Cards collapse
|
||||
to compact pills (title only). */
|
||||
.inline-plugins-rail--strip {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
padding: 2px 0 6px;
|
||||
max-height: 44px;
|
||||
}
|
||||
.inline-plugins-rail--strip::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
.inline-plugins-rail--strip::-webkit-scrollbar-thumb {
|
||||
background: var(--border);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__card {
|
||||
flex: 0 0 auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
max-width: 200px;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__card:hover:not(:disabled) {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__card:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: progress;
|
||||
}
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: 500;
|
||||
}
|
||||
/* Description + trust are hidden inside the strip rail — they live
|
||||
in the marketplace detail page. Keeping the strip compact is the
|
||||
only way the chat history stays visible. */
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__desc,
|
||||
.inline-plugins-rail--strip .inline-plugins-rail__trust {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inline-plugins-rail__error {
|
||||
flex: 0 0 100%;
|
||||
font-size: 11.5px;
|
||||
color: var(--danger, #c33);
|
||||
}
|
||||
|
||||
/* Context chip strip — small pill list of typed context items the
|
||||
active plugin contributed. Sits inside `.plugins-section__active`. */
|
||||
.context-chip-strip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 6px;
|
||||
align-items: center;
|
||||
}
|
||||
.context-chip-strip__empty {
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.context-chip-strip__chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 0 2px 0 0;
|
||||
border-radius: 999px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
font-size: 11px;
|
||||
color: var(--text);
|
||||
max-width: 240px;
|
||||
}
|
||||
/* Per-kind accent stripe — keeps the chip recognisable at a glance
|
||||
without leaning on color-only differentiation (the icon does most
|
||||
of the work). Uses border-left for skill, design-system, etc. */
|
||||
.context-chip-strip__chip[data-kind='skill'] {
|
||||
border-left: 3px solid var(--accent, #6e7bff);
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='design-system'] {
|
||||
border-left: 3px solid #d6a648;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='craft'] {
|
||||
border-left: 3px solid #6dbf6d;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='asset'] {
|
||||
border-left: 3px solid #8a8a8a;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='mcp'] {
|
||||
border-left: 3px solid #c47bd9;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='claude-plugin'] {
|
||||
border-left: 3px solid #d97757;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='atom'] {
|
||||
border-left: 3px solid #5fb8d1;
|
||||
}
|
||||
.context-chip-strip__chip[data-kind='plugin'] {
|
||||
border-left: 3px solid var(--accent, #6e7bff);
|
||||
}
|
||||
.context-chip-strip__body {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px 2px 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
cursor: default;
|
||||
text-align: left;
|
||||
border-radius: 999px;
|
||||
min-width: 0;
|
||||
}
|
||||
.context-chip-strip__chip.is-interactive .context-chip-strip__body {
|
||||
cursor: pointer;
|
||||
}
|
||||
.context-chip-strip__chip.is-interactive .context-chip-strip__body:hover {
|
||||
background: var(--bg-hover, rgba(255, 255, 255, 0.04));
|
||||
}
|
||||
.context-chip-strip__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.context-chip-strip__chip.is-interactive .context-chip-strip__body:hover
|
||||
.context-chip-strip__icon {
|
||||
color: var(--accent, var(--text));
|
||||
}
|
||||
.context-chip-strip__kind {
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.03em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.context-chip-strip__label {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.context-chip-strip__remove {
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.context-chip-strip__remove:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* Plugin inputs form — appears when the active plugin declares
|
||||
`od.inputs[]`. Compact stacked label/input pairs. */
|
||||
.plugin-inputs-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.plugin-inputs-form__field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
}
|
||||
.plugin-inputs-form__label {
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.plugin-inputs-form__required {
|
||||
color: var(--danger, #c33);
|
||||
margin-left: 2px;
|
||||
}
|
||||
.plugin-inputs-form__input {
|
||||
font: inherit;
|
||||
font-size: 12.5px;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm, 6px);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
.plugin-inputs-form__input:focus {
|
||||
outline: 1px solid var(--accent);
|
||||
outline-offset: 0;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.plugin-inputs-form__input--textarea {
|
||||
min-height: 56px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
1993
apps/web/src/styles/viewer/routines.css
Normal file
1993
apps/web/src/styles/viewer/routines.css
Normal file
File diff suppressed because it is too large
Load diff
2285
apps/web/src/styles/viewer/templates-plugins.css
Normal file
2285
apps/web/src/styles/viewer/templates-plugins.css
Normal file
File diff suppressed because it is too large
Load diff
1477
apps/web/src/styles/viewer/theater.css
Normal file
1477
apps/web/src/styles/viewer/theater.css
Normal file
File diff suppressed because it is too large
Load diff
875
apps/web/src/styles/viewer/tools.css
Normal file
875
apps/web/src/styles/viewer/tools.css
Normal file
|
|
@ -0,0 +1,875 @@
|
|||
/*
|
||||
* Inline rendering of Claude's AskUserQuestion tool. Question text reads
|
||||
* like a normal assistant message, options live as a row of chip buttons
|
||||
* the user picks from. Once an answer ships (or the user has moved past
|
||||
* the turn) the card locks into a read only state: chips remain visible
|
||||
* but unclickable so the user can see what was asked.
|
||||
*/
|
||||
.op-ask-question .op-card-head { padding-bottom: 4px; }
|
||||
.op-ask-question-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
padding: 4px 12px 12px;
|
||||
}
|
||||
.op-ask-question-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.op-ask-question-header {
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.op-ask-question-prompt {
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: var(--text);
|
||||
}
|
||||
.op-ask-question-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.op-ask-question-option {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
padding: 7px 11px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
font-size: 12.5px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
transition: border-color 120ms ease, background 120ms ease;
|
||||
}
|
||||
.op-ask-question-option:hover { border-color: var(--accent); }
|
||||
.op-ask-question-option.on {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-bg, var(--bg-subtle));
|
||||
}
|
||||
.op-ask-question-option:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.op-ask-question-option-label { font-weight: 500; }
|
||||
.op-ask-question-option-desc {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
white-space: normal;
|
||||
}
|
||||
.op-ask-question-foot {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 12px 12px;
|
||||
}
|
||||
/* Match the composer's Send button styling so the two actions read as
|
||||
* the same control family. The composer-send is bordered with the
|
||||
* accent color and uses the same padding/typography; we drop the icon
|
||||
* by skipping the gap/svg children entirely. */
|
||||
.op-ask-question-submit {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
color: white;
|
||||
font-weight: 500;
|
||||
padding: 4px 14px;
|
||||
font-size: 12.5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.op-ask-question-submit:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
}
|
||||
.op-ask-question-submit:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
/*
|
||||
* Locked card state: unchosen options fade back into the card surface
|
||||
* while the user's pick keeps its accent outline so the answer stays
|
||||
* legible after submission. Without the `:not(.on)` qualifier the lock
|
||||
* would wipe the selected state and the card would look unanswered.
|
||||
*/
|
||||
.op-ask-question-locked .op-ask-question-option:not(.on) {
|
||||
border-color: var(--border-soft);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.op-ask-question-locked .op-ask-question-option.on {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent-bg, var(--bg-subtle));
|
||||
cursor: default;
|
||||
}
|
||||
.op-ask-question-locked .op-ask-question-option.on .op-ask-question-option-desc {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.op-meta { color: var(--text-muted); font-size: 11.5px; }
|
||||
.op-desc { font-style: italic; }
|
||||
.op-path {
|
||||
background: var(--bg-subtle);
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-family: var(--mono);
|
||||
color: var(--text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: min(220px, 100%);
|
||||
}
|
||||
.op-status {
|
||||
margin-left: auto;
|
||||
font-size: 10.5px;
|
||||
padding: 2px 9px;
|
||||
border-radius: var(--radius-pill);
|
||||
letter-spacing: 0.02em;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.op-status-running {
|
||||
border-color: var(--purple-border);
|
||||
background: var(--purple-bg);
|
||||
color: var(--purple);
|
||||
animation: pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
/*
|
||||
* Static waiting-for-input variant. Same purple chrome as `op-status-running`
|
||||
* but no pulse: an AskUserQuestion card is parked until the user clicks, not
|
||||
* actively working, so the animation is misleading.
|
||||
*/
|
||||
.op-status-awaiting {
|
||||
border-color: var(--purple-border);
|
||||
background: var(--purple-bg);
|
||||
color: var(--purple);
|
||||
}
|
||||
.op-status-ok { border-color: var(--green-border); background: var(--green-bg); color: var(--green); }
|
||||
.op-status-error { border-color: var(--red-border); background: var(--red-bg); color: var(--red); }
|
||||
.op-toggle {
|
||||
font-size: 10.5px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.op-open {
|
||||
font-size: 10.5px;
|
||||
padding: 2px 9px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-pill);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.op-open:hover {
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.produced-files {
|
||||
margin-top: 4px;
|
||||
padding: 12px 14px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.produced-files-label {
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.produced-files-list { display: flex; flex-direction: column; gap: 4px; }
|
||||
.produced-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
font-size: 12px;
|
||||
}
|
||||
.produced-file-icon { width: 22px; text-align: center; color: var(--text-muted); }
|
||||
.produced-file-name {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: var(--mono);
|
||||
}
|
||||
.produced-file-size {
|
||||
font-size: 10.5px;
|
||||
color: var(--text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.produced-file-actions { display: flex; gap: 6px; }
|
||||
.produced-file-actions .ghost,
|
||||
.produced-file-actions .ghost-link {
|
||||
font-size: 11px;
|
||||
padding: 3px 9px;
|
||||
}
|
||||
|
||||
.plugin-action-panel {
|
||||
margin-top: 4px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--accent-soft);
|
||||
border-radius: var(--radius);
|
||||
background: color-mix(in srgb, var(--accent) 5%, var(--bg-panel));
|
||||
box-shadow: var(--shadow-xs);
|
||||
}
|
||||
.plugin-action-panel__head {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 9px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.plugin-action-panel__icon {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 7px;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.plugin-action-panel__title {
|
||||
font-weight: 650;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
}
|
||||
.plugin-action-panel__subtitle {
|
||||
margin-top: 2px;
|
||||
font-size: 11.5px;
|
||||
line-height: 1.35;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.plugin-action-panel__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.plugin-action-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
padding-top: 8px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
}
|
||||
.plugin-action-panel__list .plugin-action-card:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
}
|
||||
.plugin-action-card__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.plugin-action-card__folder-icon {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.plugin-action-card__copy {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 5px 8px;
|
||||
min-width: 0;
|
||||
font-size: 11.5px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.plugin-action-card__path {
|
||||
max-width: 220px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-soft);
|
||||
color: var(--text);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.plugin-action-card__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.plugin-action-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
min-height: 28px;
|
||||
max-width: 100%;
|
||||
padding: 5px 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 7px;
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
font-size: 11.5px;
|
||||
font-weight: 550;
|
||||
cursor: pointer;
|
||||
}
|
||||
.plugin-action-button:hover:not(:disabled) {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.plugin-action-button:disabled {
|
||||
opacity: 0.58;
|
||||
cursor: default;
|
||||
}
|
||||
.plugin-action-button--primary {
|
||||
border-color: var(--accent);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
.plugin-action-button--primary:hover:not(:disabled) {
|
||||
color: #fff;
|
||||
filter: brightness(0.98);
|
||||
}
|
||||
.plugin-action-card__notice {
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-soft);
|
||||
color: var(--text-muted);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.plugin-action-card__notice a {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* "Files this turn" summary — derived from Read/Write/Edit tool_use events
|
||||
so users can scan every file the agent touched without expanding tool-
|
||||
group disclosures. Compact pill while streaming, full list once done. */
|
||||
.file-ops {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-panel);
|
||||
overflow: hidden;
|
||||
}
|
||||
.file-ops.is-streaming {
|
||||
border-style: dashed;
|
||||
background: var(--bg-subtle);
|
||||
}
|
||||
.file-ops-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
padding: 7px 10px;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
color: var(--text);
|
||||
min-width: 0;
|
||||
}
|
||||
.file-ops-toggle:hover { background: var(--bg-subtle); }
|
||||
.file-ops-icon { display: inline-flex; color: var(--text-muted); }
|
||||
.file-ops-label {
|
||||
font-size: 10.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
.file-ops-summary-line {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.file-ops-count {
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--text-muted);
|
||||
padding: 1px 6px;
|
||||
border-radius: 8px;
|
||||
background: var(--bg-subtle);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.file-ops.is-streaming .file-ops-count {
|
||||
color: var(--accent);
|
||||
border-color: color-mix(in oklab, var(--accent) 30%, var(--border));
|
||||
}
|
||||
.file-ops-chev { display: inline-flex; color: var(--text-muted); }
|
||||
.file-ops-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 4px 6px 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
border-top: 1px solid var(--border-soft, var(--border));
|
||||
}
|
||||
.file-ops-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
.file-ops-row:hover { background: var(--bg-subtle); }
|
||||
.file-ops-row--running { color: var(--text); }
|
||||
.file-ops-row--error { color: var(--text); }
|
||||
.file-ops-row-badges { display: inline-flex; gap: 3px; flex-shrink: 0; }
|
||||
.file-ops-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
font-family: var(--mono);
|
||||
letter-spacing: 0;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-subtle);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.file-ops-badge-count {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.85;
|
||||
}
|
||||
.file-ops-badge--read {
|
||||
color: color-mix(in oklab, var(--accent) 80%, var(--text));
|
||||
border-color: color-mix(in oklab, var(--accent) 30%, var(--border));
|
||||
}
|
||||
.file-ops-badge--write {
|
||||
color: #2e7d32;
|
||||
border-color: color-mix(in oklab, #2e7d32 35%, var(--border));
|
||||
background: color-mix(in oklab, #2e7d32 8%, var(--bg-subtle));
|
||||
}
|
||||
.file-ops-badge--edit {
|
||||
color: #b26500;
|
||||
border-color: color-mix(in oklab, #b26500 35%, var(--border));
|
||||
background: color-mix(in oklab, #b26500 8%, var(--bg-subtle));
|
||||
}
|
||||
.file-ops-row-path {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.file-ops-row-count {
|
||||
font-size: 10.5px;
|
||||
color: var(--text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.file-ops-row-status {
|
||||
font-size: 10.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
font-weight: 600;
|
||||
padding: 1px 6px;
|
||||
border-radius: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.file-ops-row-status--running {
|
||||
color: var(--accent);
|
||||
background: color-mix(in oklab, var(--accent) 10%, transparent);
|
||||
}
|
||||
.file-ops-row-status--error {
|
||||
color: #c0392b;
|
||||
background: color-mix(in oklab, #c0392b 12%, transparent);
|
||||
}
|
||||
.file-ops-row-open {
|
||||
font-size: 11px;
|
||||
padding: 3px 9px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 6px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.file-ops-row-open:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.op-bash .op-command,
|
||||
.op-bash .op-output {
|
||||
margin: 0;
|
||||
padding: 8px 12px;
|
||||
background: #1c1b1a;
|
||||
color: #f0eee9;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
line-height: 1.55;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
max-height: 220px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.op-bash .op-output { background: #2a2926; }
|
||||
|
||||
.op-todo .todo-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 6px 8px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.todo-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 5px 8px;
|
||||
line-height: 1.45;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid transparent;
|
||||
transition: background-color 120ms ease, border-color 120ms ease;
|
||||
}
|
||||
.todo-check {
|
||||
width: 16px;
|
||||
flex-shrink: 0;
|
||||
color: var(--text-soft);
|
||||
font-family: var(--mono);
|
||||
text-align: center;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.todo-text { color: var(--text); }
|
||||
|
||||
/* Pending — quietly waiting */
|
||||
.todo-pending .todo-check { color: var(--text-faint); }
|
||||
.todo-pending .todo-text { color: var(--text-muted); }
|
||||
|
||||
/* In progress — the only row that should pop */
|
||||
.todo-in_progress {
|
||||
background: color-mix(in srgb, var(--accent) 10%, var(--bg-panel));
|
||||
border-color: color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
}
|
||||
.todo-in_progress .todo-check { color: var(--accent); }
|
||||
.todo-in_progress .todo-text { color: var(--text-strong); font-weight: 600; }
|
||||
|
||||
/* Completed — settled, but still readable */
|
||||
.todo-completed .todo-check { color: var(--green); }
|
||||
.todo-completed .todo-text {
|
||||
text-decoration: line-through;
|
||||
text-decoration-color: color-mix(in srgb, var(--text-muted) 60%, transparent);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Stopped — terminal fallback for a run that ended mid-task */
|
||||
.todo-stopped {
|
||||
background: color-mix(in srgb, var(--red) 8%, var(--bg-panel));
|
||||
border-color: color-mix(in srgb, var(--red) 24%, transparent);
|
||||
}
|
||||
.todo-stopped .todo-check { color: var(--red); font-weight: 700; }
|
||||
.todo-stopped .todo-text { color: var(--text); }
|
||||
|
||||
/* Composer extras */
|
||||
.composer.drag-active {
|
||||
outline: 2px dashed var(--accent);
|
||||
outline-offset: -4px;
|
||||
}
|
||||
|
||||
/* Present / Share menus */
|
||||
.present-wrap { position: relative; display: inline-block; }
|
||||
.present-trigger .caret {
|
||||
margin-left: 4px;
|
||||
font-size: 10px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.present-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 60;
|
||||
min-width: 168px;
|
||||
padding: 4px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.present-menu button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 8px 10px;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: var(--text);
|
||||
}
|
||||
.present-menu button:hover { background: var(--bg-subtle); border-color: transparent; }
|
||||
.present-icon {
|
||||
display: inline-flex;
|
||||
width: 14px;
|
||||
justify-content: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.share-menu { position: relative; display: inline-block; }
|
||||
.viewer-action-export {
|
||||
gap: 6px;
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: white;
|
||||
box-shadow: 0 6px 14px color-mix(in srgb, var(--accent) 20%, transparent);
|
||||
}
|
||||
.viewer-action-export:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
border-color: var(--accent-hover);
|
||||
color: white;
|
||||
}
|
||||
@keyframes export-ready-nudge {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 6px 14px color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
}
|
||||
18% {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent),
|
||||
0 8px 18px color-mix(in srgb, var(--accent) 30%, transparent);
|
||||
}
|
||||
36% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
54% {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 12%, transparent),
|
||||
0 8px 18px color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 6px 14px color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.chrome-action-export.export-ready-nudge {
|
||||
animation: export-ready-nudge-reduced 1200ms ease-out 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes export-ready-nudge-reduced {
|
||||
0%, 100% {
|
||||
box-shadow: 0 6px 14px color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
}
|
||||
35% {
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
}
|
||||
.share-menu-popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
min-width: 240px;
|
||||
padding: 4px;
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.share-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 10px;
|
||||
font-size: 12.5px;
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
color: var(--text);
|
||||
}
|
||||
.share-menu-item:hover:not(:disabled) {
|
||||
background: var(--bg-subtle);
|
||||
border-color: transparent;
|
||||
}
|
||||
.share-menu-item:disabled { opacity: 0.45; cursor: not-allowed; }
|
||||
.share-menu-icon { flex: 0 0 auto; width: 18px; text-align: center; font-size: 13px; }
|
||||
.share-menu-divider { height: 1px; background: var(--border); margin: 4px 6px; }
|
||||
|
||||
.button-like {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg-panel);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.button-like:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.deploy-modal {
|
||||
width: min(760px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
overflow: auto;
|
||||
}
|
||||
/* Add a few extra pixels above the deploy dialog's primary action so
|
||||
it does not crowd the divider. The shared .modal-foot uses 12px
|
||||
vertical padding which is fine for shorter dialogs but reads as
|
||||
cramped here because the deploy form ends in dense token / domain
|
||||
config rows that push content right up to the border. Scoped to the
|
||||
deploy dialog only (the template-save dialog also carries the
|
||||
.deploy-modal class on its root, which is why we key off the
|
||||
purpose-specific .deploy-flow-modal hook instead). 16px matches the
|
||||
bump proposed for the global rule in #957 so the rhythm stays close
|
||||
to the rest of the app. Issue #913. */
|
||||
.deploy-flow-modal .modal-foot {
|
||||
padding-top: 16px;
|
||||
}
|
||||
.deploy-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.deploy-provider-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.field-label-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
}
|
||||
.field-label-note {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 4px;
|
||||
max-width: min(420px, 70%);
|
||||
text-align: right;
|
||||
}
|
||||
.field-label-note .hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.field-label-row a:not(.field-label-link) {
|
||||
color: var(--accent);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.field-label-group {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.field-label-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 10.5px;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
transition: color 120ms ease;
|
||||
}
|
||||
.field-label-link:hover {
|
||||
color: var(--accent);
|
||||
}
|
||||
.field-label-link svg {
|
||||
opacity: 0.75;
|
||||
transition: transform 120ms ease, opacity 120ms ease;
|
||||
}
|
||||
.field-label-link:hover svg {
|
||||
opacity: 1;
|
||||
transform: translate(1px, -1px);
|
||||
}
|
||||
.field-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
padding: 2px 7px;
|
||||
border-radius: var(--radius-pill);
|
||||
color: #137a3d;
|
||||
background: color-mix(in srgb, #1f9d55 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, #1f9d55 28%, var(--border));
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/*
|
||||
* Inline variant lives next to a label on the same row instead of taking
|
||||
* its own line. We also drop the green "success" treatment because the
|
||||
* media-provider row already has a green "Integrated" badge — two green
|
||||
* pills in one row read as duplicate state. Neutral muted gray keeps it
|
||||
* legible as metadata without competing for attention.
|
||||
*/
|
||||
.field-status-badge--inline {
|
||||
color: var(--text-muted);
|
||||
background: var(--bg-subtle);
|
||||
border-color: var(--border);
|
||||
font-weight: 500;
|
||||
}
|
||||
2179
apps/web/src/styles/workspace/artifacts.css
Normal file
2179
apps/web/src/styles/workspace/artifacts.css
Normal file
File diff suppressed because it is too large
Load diff
1757
apps/web/src/styles/workspace/connectors.css
Normal file
1757
apps/web/src/styles/workspace/connectors.css
Normal file
File diff suppressed because it is too large
Load diff
1005
apps/web/src/styles/workspace/design-files.css
Normal file
1005
apps/web/src/styles/workspace/design-files.css
Normal file
File diff suppressed because it is too large
Load diff
1511
apps/web/src/styles/workspace/drawer.css
Normal file
1511
apps/web/src/styles/workspace/drawer.css
Normal file
File diff suppressed because it is too large
Load diff
2160
apps/web/src/styles/workspace/mention-home.css
Normal file
2160
apps/web/src/styles/workspace/mention-home.css
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,13 +1,12 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { ChatComposer } from '../../src/components/ChatComposer';
|
||||
import { ANNOTATION_EVENT } from '../../src/components/PreviewDrawOverlay';
|
||||
import { uploadProjectFiles } from '../../src/providers/registry';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
import type { ChatAttachment, ChatCommentAttachment } from '../../src/types';
|
||||
|
||||
vi.mock('../../src/providers/registry', async () => {
|
||||
|
|
@ -355,7 +354,7 @@ describe('ChatComposer /search command', () => {
|
|||
});
|
||||
|
||||
it('keeps staged image preview modal styling available', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
|
||||
expect(css).toContain('.staged-preview-modal');
|
||||
expect(css).toContain('position: fixed;');
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import type { InspectOverrideMap } from '../../src/components/FileViewer';
|
|||
import type { LiveArtifact, LiveArtifactWorkspaceEntry, PreviewComment, ProjectFile } from '../../src/types';
|
||||
import { I18nProvider } from '../../src/i18n';
|
||||
import type { Dict } from '../../src/i18n/types';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
|
|
@ -78,6 +79,14 @@ function srcDocActivationMessages(calls: readonly (readonly unknown[])[]) {
|
|||
}
|
||||
|
||||
describe('FileViewer preview scale', () => {
|
||||
it('keeps file viewer selectors in the effective global stylesheet', () => {
|
||||
const css = readExpandedIndexCss();
|
||||
|
||||
expect(css).toContain('.viewer');
|
||||
expect(css).toContain('.viewer-toolbar');
|
||||
expect(css).toContain('.viewer-action');
|
||||
});
|
||||
|
||||
it('uses the requested zoom for desktop preview overlays', () => {
|
||||
expect(effectivePreviewScale('desktop', 1.5, { width: 320, height: 480 })).toBe(1.5);
|
||||
});
|
||||
|
|
@ -2512,14 +2521,14 @@ function baseLiveArtifactWorkspaceEntry(
|
|||
|
||||
describe('LiveArtifactViewer', () => {
|
||||
it('hides inactive live previews even when a device viewport sets display', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
const rule = css.match(/\.live-artifact-preview-layer\.preview-viewport\[data-active='false'\]\s*\{[^}]+\}/)?.[0] ?? '';
|
||||
|
||||
expect(rule).toContain('display: none;');
|
||||
});
|
||||
|
||||
it('keeps the presentation exit button aligned with preview chrome spacing', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
const rule = css.match(/\.present-exit\s*\{[^}]+\}/)?.[0] ?? '';
|
||||
|
||||
expect(rule).toContain('top: calc(env(safe-area-inset-top, 0px) + 20px);');
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../../helpers/read-expanded-css';
|
||||
|
||||
describe('Critique Theater styles', () => {
|
||||
it('keeps the Theater UI selectors in the global stylesheet', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
|
||||
expect(css).toContain('.theater-stage');
|
||||
expect(css).toContain('.theater-lane');
|
||||
|
|
|
|||
23
apps/web/tests/helpers/read-expanded-css.ts
Normal file
23
apps/web/tests/helpers/read-expanded-css.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
function expandCssFile(filePath: string, seen = new Set<string>()): string {
|
||||
const key = filePath;
|
||||
if (seen.has(key)) {
|
||||
return '';
|
||||
}
|
||||
seen.add(key);
|
||||
|
||||
const css = readFileSync(filePath, 'utf8');
|
||||
return css.replace(/@import\s+(?:url\(([^)]+)\)|(['"])([^'"]+)\2);/g, (_match, urlImport, _quote, quotedImport) => {
|
||||
const specifier = (quotedImport ?? urlImport ?? '').trim().replace(/^['"]|['"]$/g, '');
|
||||
if (!specifier.startsWith('./') && !specifier.startsWith('../')) {
|
||||
return '';
|
||||
}
|
||||
return expandCssFile(join(dirname(filePath), specifier), seen);
|
||||
});
|
||||
}
|
||||
|
||||
export function readExpandedIndexCss(): string {
|
||||
return expandCssFile(join(process.cwd(), 'src/index.css'));
|
||||
}
|
||||
|
|
@ -2,8 +2,9 @@ import { readFileSync } from 'node:fs';
|
|||
|
||||
import postcss, { type Declaration, type Root, type Rule } from 'postcss';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
const indexCss = readFileSync(new URL('../../src/index.css', import.meta.url), 'utf8');
|
||||
const indexCss = readExpandedIndexCss();
|
||||
const tasksCss = readFileSync(
|
||||
new URL('../../src/styles/home/tasks.css', import.meta.url),
|
||||
'utf8',
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
const indexCss = readFileSync(new URL('../../src/index.css', import.meta.url), 'utf8');
|
||||
const indexCss = readExpandedIndexCss();
|
||||
|
||||
function cssBlock(selector: string): string {
|
||||
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
const indexCss = readFileSync(new URL('../../src/index.css', import.meta.url), 'utf8');
|
||||
const indexCss = readExpandedIndexCss();
|
||||
|
||||
function cssBlock(selector: string): string {
|
||||
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import postcss, { type Rule } from 'postcss';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
describe('plugin info preview pane styles', () => {
|
||||
it('keeps plugin preview sidebar content away from the pane edges', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
const root = postcss.parse(css, { from: 'src/index.css' });
|
||||
const topLevelRules = root.nodes.filter(
|
||||
(node): node is Rule => node.type === 'rule',
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
describe('plugin share confirmation styles', () => {
|
||||
it('keeps the publish dialog footer away from the modal edge', () => {
|
||||
const css = readFileSync(join(process.cwd(), 'src/index.css'), 'utf8');
|
||||
const css = readExpandedIndexCss();
|
||||
|
||||
expect(css).toContain('.plugin-share-confirm .plugin-details-modal__foot');
|
||||
expect(css).toContain('padding: 16px 24px 22px;');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { readExpandedIndexCss } from '../helpers/read-expanded-css';
|
||||
|
||||
const indexCss = readFileSync(new URL('../../src/index.css', import.meta.url), 'utf8');
|
||||
const indexCss = readExpandedIndexCss();
|
||||
const pluginsHomeCss = readFileSync(
|
||||
new URL('../../src/styles/home/plugins-home.css', import.meta.url),
|
||||
'utf8',
|
||||
|
|
|
|||
Loading…
Reference in a new issue