mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
* feat(runtimes): register AMR (vela) as an ACP stdio agent
AMR is the vela CLI's ACP runtime mode. `vela agent run --runtime opencode`
speaks ACP JSON-RPC over stdio (see vela's
`specs/current/runtime/manual-agent-run-openrouter.md`); per
`docs/new-agent-runtime-acp.md` we expose it through the same `streamFormat:
'acp-json-rpc'` transport that already powers Hermes, Devin, Kimi, etc.
The new `defs/amr.ts` is the entire wiring — `buildArgs` returns
`['agent', 'run', '--runtime', 'opencode']`, `fetchModels` reuses
`detectAcpModels`, and the fallback list seeds the OpenRouter ids vela's
e2e baseline uses. `executables.ts`/`app-config.ts`/`metadata.ts` get the
matching `VELA_BIN`/`VELA_LINK_URL`/`VELA_RUNTIME_KEY`/`VELA_OPENCODE_BIN`
allowlist + install/docs URLs, so users can configure the per-agent env in
Settings without leaking into other adapters.
Coverage: `tests/fixtures/fake-vela.mjs` is a minimal ACP stub that returns
the documented `initialize` / `session/new` / `session/set_model` /
`session/prompt` shapes; `tests/amr-acp-integration.test.ts` spawns it via
`child_process.spawn` and drives a full turn through `attachAcpSession` and
`detectAcpModels`, so the ACP transport contract for AMR is end-to-end
verified locally even before a real `vela` binary is installed.
Validated:
- pnpm guard
- pnpm typecheck (all workspace projects)
- pnpm --filter @open-design/daemon test (2881/2881)
Deferred: real OpenRouter-backed turn through a built `vela` binary —
the runtime def needs no changes for that path, only `VELA_RUNTIME_KEY`
and `VELA_LINK_URL` in env (or Settings).
* fix(runtimes/amr): pin a concrete default model and bare openai ids
End-to-end validation against a freshly-built `vela` (nexu-io/vela@main)
+ OpenRouter surfaced two contract details the first AMR runtime def
got wrong:
1. vela rejects `session/prompt` with `session/set_model must be called
before session/prompt`. attachAcpSession in apps/daemon/src/acp.ts
skips set_model whenever the picked model is the synthetic 'default'
id, so AMR's fallback list must NOT include DEFAULT_MODEL_OPTION. The
def now ships a concrete `gpt-5.4-mini` as both `fetchModels`'
default option and `fallbackModels[0]`, which makes attachAcpSession
always send a real `session/set_model` for AMR turns.
2. `vela --runtime opencode` auto-prepends `openai/` to whatever modelId
it forwards to opencode's openai provider. With OpenRouter-style ids
like `openai/gpt-5.4-mini`, opencode receives the double-prefixed
`openai/openai/gpt-5.4-mini` and replies `ProviderModelNotFoundError`.
The new fallback list ships the bare ids opencode's openai registry
actually knows about (gpt-5.4, gpt-5.4-mini, gpt-5.4-fast, etc.).
Stub + tests:
- tests/fixtures/fake-vela.mjs now enforces the set_model gate the same
way real vela does, so a regression that silently goes back to
model: 'default' would surface as a fatal error in tests instead of a
hidden production failure.
- tests/amr-acp-integration.test.ts pins both contracts: no 'default' /
no 'openai/' prefix in fallbackModels, and a negative case that
asserts session/prompt fails when no model is set.
Adds `apps/daemon/scripts/verify-amr-real-vela.mjs` — a small dev-time
runner that drives `attachAcpSession` against a real `vela` binary and
prints the daemon's chat events, so future protocol drift can be checked
against an actual OpenRouter call.
Verified locally: `vela agent run --runtime opencode` + OpenRouter
returns the prompted string ("AMR-E2E-PASS") through the full daemon
pipeline; daemon test suite stays 2883/2883.
* fix(runtimes/amr): substitute concrete model when chat run sends 'default'
A plugin-driven AMR run from the UI surfaced a real-world hole in the
prior commit:
json-rpc id 3: session/set_model must be called before session/prompt
The Default-design-router plugin (and any caller that doesn't pin a
real model) sends `model: 'default'` straight through, which the AMR
runtime def cannot accept — vela rejects `session/prompt` without
`session/set_model` and attachAcpSession skips set_model whenever
model === 'default'. Just leaving DEFAULT_MODEL_OPTION out of the
adapter's `fallbackModels` is not enough: the chat-run handler in
server.ts still forwarded 'default' verbatim.
This adds `resolveModelForAgent(def, resolved, env?)` as the
single source of truth for the substitution:
1. If the caller picked a real id, pass it through.
2. Else, if `def.defaultModelEnvVar` is set and the daemon process
env has a non-empty value for it, return that (operator escape
hatch — see below).
3. Else, if the def's `fallbackModels` does NOT contain a 'default'
id, return `fallbackModels[0].id`.
4. Else, return the original value (the historic shape — defs that
list 'default' themselves are untouched).
AMR sets `defaultModelEnvVar: 'VELA_DEFAULT_MODEL'`, so when
opencode's openai-provider registry deprecates `gpt-5.4-mini`
upstream, an operator can swap the fallback id without a code change
by exporting `VELA_DEFAULT_MODEL=gpt-5.5` before launching tools-dev
/ od. Worth noting the env var must live in the daemon's `process.env`
(Settings-UI per-agent env values only reach the spawned child, not
the daemon's resolver) — the new field's docblock spells this out.
Coverage:
- `tests/runtimes/resolve-model.test.ts` — 8 unit tests covering all
four resolver branches plus the env-override happy path / fallback /
ignore-when-user-picked-a-real-id case.
- `pnpm --filter @open-design/daemon typecheck` clean.
* chore(runtimes/amr): move AMR to the top of the base agent list
So `AMR (vela)` shows up first in the agent picker / status views,
ahead of claude / codex. Pure ordering change; no behavior delta.
* feat(amr): Sign-in / Sign-out button on the AMR Settings card
The first half of the AMR work assumed the operator would set
VELA_RUNTIME_KEY / VELA_LINK_URL on the daemon process and never
surfaced login state to users. This adds the missing UX so a fresh
install can drive the full path from Settings:
- GET /api/integrations/vela/status reads ~/.vela/config.json
for the active profile and returns { loggedIn, profile, user }
(without leaking the runtime/control keys themselves).
- POST /api/integrations/vela/login spawns `vela login` once
(409 if one is already in flight). The vela CLI opens the user's
browser to the device-authorization page itself — Open Design
only needs to kick the subprocess off.
- POST /api/integrations/vela/logout removes ~/.vela/config.json
so the next status read returns logged-out.
`AmrAgentCard` is a dedicated agent-card component for AMR because
the existing `<button>` row can't host an interactive sub-control
(nested interactive elements). It polls /status after a login click
until the daemon reports loggedIn=true (or 5 minutes elapse), and
exposes a Sign-out action on hover. Other adapters (claude, codex,
hermes, …) keep their existing `<button>` card.
i18n: 8 new keys (settings.amrLogin / Logout / LoggingIn / etc.)
added to en + zh-CN. Other locales spread `en` and inherit the
English copy until translations land.
Coverage:
- `tests/integrations/vela.test.ts` pins the config.json reader
against a tmp HOME — including the negative case where a profile
has user info but no runtimeKey (still logged-out), and the
secret-leak guard ("rt-secret-*" must not appear in the projection
payload).
- `tests/components/AmrAgentCard.test.tsx` covers all four UI
states (logged-out, logging-in, logged-in, logging-out) plus the
click-propagation invariant the divergent card was built to keep.
`pnpm --filter @open-design/daemon test` 2901 / 2901 passing.
`pnpm --filter @open-design/web test` 1719 / 1719 passing.
`pnpm typecheck` + `pnpm guard` clean.
Dev script side-effects: `apps/daemon/scripts/verify-amr-real-vela.mjs`
no longer requires both VELA_RUNTIME_KEY and VELA_LINK_URL — if
VELA_PROFILE is set, the vela CLI is allowed to resolve credentials
from `~/.vela/config.json`. Added the two AMR `.mjs` fixtures to
`scripts/guard.ts` allowlist with the executable-fixture / dev-runner
rationale.
* fix(connection-test): substitute model for AMR before attachAcpSession
The chat-run path in server.ts already routes the requested model through
`resolveModelForAgent` so AMR / vela (whose CLI demands an explicit
`session/set_model` before `session/prompt`) gets the def's first
concrete fallback id when the chat run ships `model: 'default'`.
`connectionTest.ts` was wiring `attachAcpSession({ ..., model: model ?? null })`
directly, which made the Test Connection button on the AMR Settings
card deadlock with the same `session/set_model must be called before
session/prompt` error the chat-run path already handles — surfaced as a
permanent "Testing connection…" spinner in the UI.
Reuse the same helper here so Test Connection mirrors chat-run behavior.
* test(amr): three-layer end-to-end coverage for the AMR login + turn flow
The PR up to this point shipped runtime + UI code with unit-level Vitest
coverage. This commit adds the cross-layer regression net the live demo
relied on:
1. apps/daemon/tests/integrations/vela.routes.test.ts (HTTP, Vitest)
Spins up the real daemon Express app via `startServer({port:0,...})`,
persists `agentCliEnv.amr.VELA_BIN = <fake>` into app-config.json,
and exercises every /api/integrations/vela/* endpoint against the
extended fake-vela stub:
- status reads ~/.vela/config.json under various states
- login spawns the fake, waits for config.json to appear, returns
pid + startedAt + profile
- 409 already-running guard with the stub's delay knob
- logout removes the file (idempotent)
- secrets (runtimeKey / controlKey) never leak in the projection
- login → status round-trip flips loggedIn=false → true
2. e2e/tests/amr/turn.test.ts (tools-dev orchestrated, Vitest)
Boots a namespaced daemon + web pair through `createSmokeSuite`,
inlines a self-contained fake `vela` binary that handles BOTH
`vela login` (writes ~/.vela/config.json) and
`vela agent run --runtime opencode` (ACP stdio with the
`session/set_model must precede session/prompt` gate the real binary
enforces), then drives a complete /api/runs lifecycle for
`agentId: 'amr', model: 'default'` and asserts the assistant message
captures the fake's streamed text. This is the test that would have
surfaced today's plugin-default-model regression (the `set_model
before prompt` error) at PR time instead of demo time.
3. e2e/ui/amr-login-pill.test.ts (Playwright)
Mocks /api/agents + /api/integrations/vela/{status,login,logout}
to drive the Settings AMR card through the full Sign in → Signed in
→ Sign out cycle. Pins the AmrLoginPill polling contract and the
aria-label semantics (the pill's accessible name is "Sign out" once
logged in, regardless of which label the hover-state text shows).
fake-vela.mjs extensions:
- Handles `vela login` argv by writing
~/.vela/config.json for the active VELA_PROFILE and exiting 0 —
mirrors real vela's on-disk side-effect without the device-auth
loop.
- FAKE_VELA_LOGIN_DELAY_MS knob so route tests can observe the
in-flight state of the spawn lifecycle.
- FAKE_VELA_LOGIN_USER_EMAIL / _USER_PLAN to assert the surfaced
user fields end-to-end.
Validated:
- `pnpm guard` + `pnpm typecheck` (all workspace projects)
- `pnpm --filter @open-design/daemon test`: 2998 / 2998 passing,
including the new 8-test integration suite.
- `cd e2e && pnpm test tests/amr`: 1 / 1 passing.
- `cd e2e && pnpm exec playwright test ui/amr-login-pill.test.ts`:
1 / 1 passing (6.7s).
* feat(amr): package native cli and refine login ui
* feat(amr): wire vela cli beta packaging
* docs(amr): document vela ci packaging review
* docs(amr): refine vela ci integration review
* fix(ci): refresh nix pnpm dependency hashes
* fix(pack): clean up Vela CLI packaging
* fix(pack): bundle Vela CLI support files
* fix(amr): recover login attempts from stale auth state
* test: expand AMR and automations coverage
* fix(amr): address review follow-ups
* test(web): align tasks fixtures with contracts
* fix(daemon): type wildcard route params
* fix(ci): refresh PR merge validation
* fix(amr): clear env credentials on logout
* feat(settings): inline local CLI model configuration
* fix(amr): recognize daemon env credentials
* [codex] Fix Vela companion packaging (#2979)
* Fix Vela companion packaging
* Update Nix pnpm dependency hashes
* [codex] Surface AMR account failures (#2980)
* fix: surface AMR account failures
* fix: cover AMR recovery error guidance
* chore: bump beta base version to 0.8.1 (#2990)
* Fix AMR profile and packaged runtime review issues
* Detect packaged AMR OpenCode companion tree
* feat(web): polish AMR frontend flows
* Polish AMR onboarding card
* fix: read AMR login state from dot-amr config (#3048)
* test: tighten AMR credential and packaging coverage
* test: restore AMR executable test env helper
* [codex] Fix packaged mac Dock identity and AMR label (#3076)
* Fix packaged mac sidecar Dock identity
* Rename AMR assistant label
* Fix AMR live models and dot-amr login state (#3073)
* fix: read AMR login state from dot-amr config
* fix: load live AMR models before runs
* fix: point AMR onboarding link to production wallet
* fix: address AMR model review feedback
* fix: persist live AMR model fallback
* [codex] Fix AMR link catalog model ids (#3088)
* Fix packaged mac sidecar Dock identity
* Rename AMR assistant label
* Fix AMR link catalog model ids
* Fix AMR model normalization typecheck
* Use live AMR model for default runs
* fix: polish AMR runtime settings UI
* Accelerate AMR startup defaults (#3092)
* Surface AMR insufficient balance wallet URL (#3099)
* fix(web): polish onboarding controls (#3112)
* fix(web): show CLI scan loading state
* Avoid duplicate AMR wallet recharge links (#3117)
* Avoid duplicate AMR wallet recharge links
* Use Vela CLI 0.0.3 test package
* chore(nix): refresh pnpm deps hash
* Fix AMR wallet guidance display
---------
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
* chore(pack): pin Vela CLI 0.0.3-test.1 (#3127)
* chore(nix): refresh pnpm deps hash
* chore(pack): pin Vela CLI 0.0.3
* chore(nix): refresh pnpm deps hash
* fix(web): suppress AMR exit 130 fallback (#3136)
* feat(web): nudge users to hosted AMR on model/auth/quota failures (#3083)
* feat(web): nudge users to hosted AMR on model/auth/quota failures
When a non-AMR agent run fails with an auth / quota / upstream model
error, surface an inline nudge under the error pill linking to Open
Design's hosted AMR gateway (https://open-design.ai/amr). The nudge
fires `surface_view` (element=run_failed_toast) on impression and
`ui_click` (element=go_amr) on the link.
Also teach the daemon to classify CLI-agent auth/quota/upstream failures
(Claude Code, codex, ...) into specific API error codes
(AGENT_AUTH_REQUIRED / RATE_LIMITED / UPSTREAM_UNAVAILABLE) instead of
the generic AGENT_EXECUTION_FAILED, so both the error message and the
nudge key off accurate codes. AMR's own runs are excluded from the
nudge — they keep the dedicated sign-in / recharge affordances.
* feat(web): rework failed-run AMR guidance into per-case error UI
Replace the single inline nudge with a per-case failed-run experience
driven by the run's error code + agent:
- The error card is now neutral gray (was red) and always carries a
retry button; it is driven by the persisted per-message error event so
it survives a reload.
- Non-AMR agent hitting a model/auth/quota wall: a theme-color promotion
card under the error card offers "switch to AMR & retry" — switches the
run to AMR, opens Settings on the AMR card, and auto-retries once the
account signs in (ProjectView polls vela login status, independent of
the Settings pill lifecycle, with success / 5-min-timeout / unmount
exits).
- AMR agent unauthorized: clearer copy + an "authorize & retry" button.
- AMR agent out of balance: clearer copy + a "top up" button to the AMR
wallet, with manual retry.
- Settings AMR card: when opened from the nudge, it scrolls into view and
pulses, and an authorize-button coachmark (a fake hand cursor that
rises in and dismisses on hover) points at the sign-in control when not
yet authorized.
analytics: surface_view (run_failed_toast) on the promotion card and
ui_click (go_amr) on its action are retained. i18n adds chat.amrCard.*
and chat.amrError.* (en / zh-CN / zh-TW translated; other locales fall
back to en) and drops the old chat.amrErrorGuidance keys.
* fix(daemon): require status context for numeric service-failure codes
Per review on #3083: the model-service classifier matched bare HTTP
status numbers (`500`, `502`, `429`, `401`), so ordinary CLI output like
`line 500`, `read 502 bytes`, or `exit code 401` could be misclassified
as a provider outage / auth wall and wrongly surface the AMR nudge. Now
a status number only counts when it carries explicit context (`HTTP 500`,
`status 503`, `code: 401`, `502 Bad Gateway`); textual provider phrases
(overloaded, bad gateway, service unavailable, rate limit, …) are
unchanged. Adds fixtures proving unrelated numeric output stays null.
* fix(web): keep error pill for failed runs ChatPane's card doesn't cover
Per review on #3083: the per-message gray error pill was suppressed for
every persisted error status event, but ChatPane only renders the
replacement top-level error card for `retryableAssistantMessage` (the
last failed assistant). So a failed turn that is no longer last (after a
follow-up) or an older failed run in history showed neither the pill nor
the card — its error detail vanished, undercutting reload/history
survival. ChatPane now passes `errorCardOwnerId` (the assistant id whose
error the card represents); AssistantMessage suppresses only that one
pill and keeps rendering StatusPill for all other error events.
* fix(daemon): don't treat a process exit code as an HTTP status
Follow-up to review on #3083: the status-context helper accepted a bare
`code` prefix, so `exit code 401` / `process exited with code 429` still
matched and got classified as AGENT_AUTH_REQUIRED / RATE_LIMITED (the
very `exit code 401` case the comment calls out as noise). `code` now
only counts when qualified (`status code` / `error code` / `response
code`) or punctuation-bound (`code: 401`); bare `exit code N` no longer
matches. Adds fixtures for exit-code lines returning null.
* chore(web): translate AMR card / error keys for 16 remaining locales
PR #3083 added 10 new `chat.amrCard.*` / `chat.amrError.*` keys but only
provided en/zh-CN/zh-TW translations; the other 16 locales fell back to
English. Translate the card title/body, three chips, primary CTA, and
the AMR self-error (auth / balance) messages and buttons for ar, de,
es-ES, fa, fr, hu, id, it, ja, ko, pl, pt-BR, ru, th, tr, uk.
* fix(amr): address review feedback on #2355
Targeted fixes for the unresolved review threads on #2355. Each fix
includes / updates a focused test.
- runtimes/executables.ts: `packagedVelaOpenCodeCompanionTree` now
verifies the inner `opencode` executable exists + is runnable, not
just the directory. This closes the false-positive availability path
that let `detectAgents()` surface AMR as available even when the
packaged companion was empty / partially copied (mrcfps, 4 threads).
- runtimes/executables.ts: `resolveAmrOpenCodeExecutable` now prefers
the bundled `<OD_RESOURCE_ROOT>/bin/libexec/opencode/opencode` over a
stale `opencode` on the user's PATH, so packaged AMR builds can't be
hijacked by a global installation.
- web/EntryShell.tsx: when the Local CLI scan returns an available
agent and the previously-selected agent is AMR, switch the selection
to the first available local agent so the runtime and persisted
agent agree before Continue.
- server.ts (model-probe branch): for AMR, check `readVelaLoginStatus`
BEFORE rejecting on an empty live-model catalog — a signed-out user
was getting `AMR_MODEL_UNAVAILABLE` ("choose a model") instead of
the correct `AMR_AUTH_REQUIRED` (sign-in affordance).
- server.ts (default model fallback): if the user asked for the AMR
agent default and the cached id is no longer in the FRESH catalog,
fall back to `liveModels[0]` from the probe instead of rejecting the
run as `AMR_MODEL_UNAVAILABLE`.
- integrations/vela.ts: route `vela login` through
`createCommandInvocation` so an npm/Node-style `vela.cmd` / `.bat`
shim on Windows gets the correct `cmd.exe /d /s /c …` wrapping with
verbatim args (matches `execAgentFile` / chat-run spawning).
- tools/pack/src/linux.ts: in containerized Linux builds, bind-mount
the host directory of `OPEN_DESIGN_VELA_CLI_BIN` and rewrite the env
to the container-side path. The host path was being passed in as-is
even though the default container only mounts /project, /tools-pack
and cache/home — `copyOptionalVelaCliBinary` saw a missing path.
Deferred (out of scope for this PR):
- `od amr status/login/logout/cancel` CLI subcommands (AGENTS.md
UI/CLI dual-track rule, server.ts:5763) — sizable surface; tracked
for a separate focused PR.
- Strict `--require-vela-cli` for Windows + mac-x64 beta builds:
prematurely blocked — `@powerformer/vela-cli` only publishes the
`darwin-arm64` platform binary today; adding the flag elsewhere
would fail the builds. Revisit once win/x64/linux binaries ship.
* fix(amr): hoist sendAmrAccountFailure above the AMR catalog preflight (TDZ)
The new signed-out AMR branch in the catalog preflight at server.ts:10875
calls `sendAmrAccountFailure(...)` to emit AMR_AUTH_REQUIRED, but the
const declaration sat ~100 lines below at the outer function scope. Because
`const` is TDZ-aware, that branch would have thrown `ReferenceError:
Cannot access 'sendAmrAccountFailure' before initialization` for the
exact users it tries to help — defeating the original intent.
Hoist the helper to just above the AMR preflight block so it's available
to every AMR code path in this function. Behavior elsewhere is unchanged.
Also rerun the daemon test suite: `launch.test.ts > resolveAgentLaunch
uses packaged built-in Vela for AMR` was creating the
`<resourceRoot>/bin/libexec/opencode/` companion *directory* only, but
this PR's earlier tightening of `packagedVelaOpenCodeCompanionTree`
also requires the inner `opencode` executable. Add it to that fixture
to match the new contract; the test was a sibling of the executables /
env-and-detection fixtures already updated in 13fc4f4.
Addresses #2355 review (mrcfps, 2026-05-28).
* feat(web): add hover cancel for AMR login (#3158)
* feat(web): add hover cancel for AMR login
* fix(web): don't bounce AmrLoginPill back to 'Signing in…' after local cancel
Both codex-connector (P2) and looper (CHANGES_REQUESTED) on this PR
flagged the same race in the new local-cancel path: `handleCancelLogin`
dispatches `notifyAmrLoginStatusChanged('login-canceled')` immediately
after `/login/cancel` returns, but the `AMR_LOGIN_STATUS_EVENT` listener
unconditionally re-enters `refresh()` and then restarts polling
whenever `/api/integrations/vela/status` still reports
`loginInFlight: true`.
That is a real race because the daemon's `cancelVelaLogin()` only sends
SIGTERM (escalating to SIGKILL after `LOGIN_CANCEL_KILL_GRACE_MS` =
2000 ms) and keeps the child in `activeLoginProcs` until it actually
exits — so the first `/status` read after a successful cancel can
legally still come back as in-flight. Under that window the pill flips
back to 'Signing in…' and can later surface the timeout/error path even
though the user already canceled, defeating the behavior promised in
the PR description.
Fix the listener instead of every dispatch site: in the
`login-canceled` branch, after the local reset (stopPolling +
setPending(null) + clear refs), optimistically mark every subscribed
pill instance as not-in-flight (`setStatus((c) => c ? { ...c,
loginInFlight: false } : c)`) and `return` — skip the
refresh-and-reconcile branch below entirely. The next explicit refresh
(component mount, user interaction, or a `status-changed` event) will
pick up the daemon's confirmed state once the child has actually
exited.
Add a focused regression test that holds `/api/integrations/vela/status`
at `loginInFlight: true` even after a successful `/login/cancel`,
asserting that the pill stays at the Canceled → Authorize sequence and
never bounces back to 'Signing in…'. This test fails on the pre-fix
listener and passes on the new behavior; existing
'cancels an in-flight AMR sign-in…' and 'reconciles late AMR browser
completion to Signed in after local cancel' tests continue to pass.
Addresses review feedback on #3158 (chatgpt-codex-connector, nettee).
---------
Co-authored-by: lefarcen <935902669@qq.com>
---------
Co-authored-by: a1chzt <chizblank@gmail.com>
Co-authored-by: Amy <1184569493@qq.com>
Co-authored-by: Mason <jinmeihong0201@gmail.com>
Co-authored-by: Caprika <56862773+alchemistklk@users.noreply.github.com>
Co-authored-by: open-design-bot[bot] <282769551+open-design-bot[bot]@users.noreply.github.com>
2645 lines
64 KiB
CSS
2645 lines
64 KiB
CSS
/* ---------- 5. Live artifact strip ---------- */
|
|
.orbit-artifact-strip {
|
|
position: relative;
|
|
display: grid;
|
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
grid-template-rows: auto auto;
|
|
column-gap: 14px;
|
|
row-gap: 10px;
|
|
align-items: center;
|
|
padding: 14px 16px 14px 18px;
|
|
border: 1px solid color-mix(in srgb, var(--accent) 32%, var(--border));
|
|
border-radius: var(--radius);
|
|
background:
|
|
linear-gradient(135deg, color-mix(in srgb, var(--accent-tint) 60%, var(--bg-panel)) 0%, var(--bg-panel) 55%);
|
|
box-shadow: var(--shadow-xs);
|
|
overflow: hidden;
|
|
}
|
|
.orbit-artifact-strip::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 3px;
|
|
background: linear-gradient(180deg, var(--accent) 0%, color-mix(in srgb, var(--accent) 50%, transparent) 100%);
|
|
}
|
|
.orbit-artifact-strip.is-legacy {
|
|
background: var(--bg-subtle);
|
|
border-color: var(--border);
|
|
border-style: dashed;
|
|
}
|
|
.orbit-artifact-strip.is-legacy::before { display: none; }
|
|
|
|
.orbit-artifact-strip-icon {
|
|
grid-row: 1;
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 10px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--bg-panel);
|
|
border: 1px solid color-mix(in srgb, var(--accent) 18%, var(--border));
|
|
color: var(--accent-strong);
|
|
flex-shrink: 0;
|
|
}
|
|
.orbit-artifact-strip.is-legacy .orbit-artifact-strip-icon {
|
|
border-color: var(--border);
|
|
color: var(--text-muted);
|
|
}
|
|
.orbit-artifact-strip-copy {
|
|
grid-row: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
min-width: 0;
|
|
}
|
|
.orbit-artifact-strip-kicker {
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
color: var(--accent-strong);
|
|
}
|
|
.orbit-artifact-strip.is-legacy .orbit-artifact-strip-kicker {
|
|
color: var(--text-muted);
|
|
}
|
|
.orbit-artifact-strip-title {
|
|
font-size: 13.5px;
|
|
font-weight: 650;
|
|
letter-spacing: -0.01em;
|
|
color: var(--text-strong);
|
|
line-height: 1.25;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.orbit-artifact-strip-meta {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.4;
|
|
}
|
|
.orbit-artifact-strip-actions {
|
|
grid-row: 1;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.orbit-artifact-ghost {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 6px 10px;
|
|
font-size: 11.5px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: background 120ms ease, border-color 120ms ease;
|
|
}
|
|
.orbit-artifact-ghost:hover {
|
|
background: var(--bg-subtle);
|
|
border-color: var(--border-strong);
|
|
}
|
|
|
|
.orbit-artifact-open {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 7px 12px;
|
|
background: var(--accent);
|
|
color: #fff;
|
|
border: 1px solid var(--accent);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-decoration: none;
|
|
box-shadow: 0 1px 0 color-mix(in srgb, var(--accent-strong) 20%, transparent) inset, var(--shadow-xs);
|
|
transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
|
|
}
|
|
.orbit-artifact-open:hover {
|
|
background: var(--accent-hover);
|
|
border-color: var(--accent-hover);
|
|
transform: translateY(-1px);
|
|
}
|
|
.orbit-artifact-open:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.orbit-artifact-peek {
|
|
grid-column: 1 / -1;
|
|
grid-row: 2;
|
|
margin-top: 2px;
|
|
border-top: 1px solid color-mix(in srgb, var(--accent) 12%, var(--border-soft));
|
|
padding-top: 8px;
|
|
}
|
|
.orbit-artifact-strip.is-legacy .orbit-artifact-peek {
|
|
border-top-color: var(--border-soft);
|
|
}
|
|
.orbit-artifact-peek summary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 0;
|
|
cursor: pointer;
|
|
list-style: none;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.02em;
|
|
color: var(--text-muted);
|
|
user-select: none;
|
|
}
|
|
.orbit-artifact-peek summary::-webkit-details-marker { display: none; }
|
|
.orbit-artifact-peek summary svg {
|
|
transition: transform 160ms ease;
|
|
color: var(--text-soft);
|
|
}
|
|
.orbit-artifact-peek[open] summary svg { transform: rotate(90deg); }
|
|
.orbit-artifact-peek[open] summary { color: var(--text); }
|
|
.orbit-artifact-peek pre {
|
|
margin: 8px 0 0;
|
|
max-height: 240px;
|
|
overflow: auto;
|
|
padding: 12px 14px;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-panel);
|
|
color: var(--text);
|
|
font: 11.5px/1.6 var(--mono);
|
|
white-space: pre-wrap;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
@media (max-width: 620px) {
|
|
.orbit-artifact-strip {
|
|
grid-template-columns: auto minmax(0, 1fr);
|
|
}
|
|
.orbit-artifact-strip-actions {
|
|
grid-column: 1 / -1;
|
|
grid-row: auto;
|
|
justify-content: flex-end;
|
|
}
|
|
.orbit-artifact-peek {
|
|
grid-column: 1 / -1;
|
|
}
|
|
}
|
|
.settings-field-error {
|
|
color: var(--red);
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.settings-about-list {
|
|
margin: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.mcp-client-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
/* Group 1: what the MCP server does */
|
|
.mcp-capabilities-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 12px 14px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
line-height: 1.55;
|
|
}
|
|
.mcp-capabilities-label {
|
|
margin: 0;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.mcp-capabilities-list {
|
|
margin: 0;
|
|
padding-left: 18px;
|
|
font-size: 13px;
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Group 2: setup flow */
|
|
.mcp-setup-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
padding: 14px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.mcp-setup-card .ds-picker {
|
|
margin-bottom: 0;
|
|
}
|
|
.mcp-running-note {
|
|
margin: 0;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
line-height: 1.5;
|
|
}
|
|
.settings-about-list > div {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.settings-about-version-row {
|
|
justify-content: space-between;
|
|
}
|
|
.settings-about-list dt {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
.settings-about-list dd {
|
|
margin: 0;
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.settings-notify-card {
|
|
padding: 12px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.settings-notify-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
.settings-notify-card-header h4 {
|
|
margin: 0;
|
|
}
|
|
.settings-notify-card-hint {
|
|
margin: 16px 0 0;
|
|
}
|
|
.settings-about-version-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.settings-about-version-num {
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
}
|
|
.settings-about-download-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 9px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
white-space: nowrap;
|
|
}
|
|
.settings-about-download-link:hover:not(:disabled) {
|
|
background: var(--bg-subtle);
|
|
color: var(--text);
|
|
border-color: var(--border-strong);
|
|
}
|
|
.settings-about-download-link:disabled {
|
|
opacity: 0.5;
|
|
cursor: default;
|
|
}
|
|
|
|
.media-provider-reload-row {
|
|
display: flex;
|
|
justify-content: flex-start;
|
|
margin-bottom: 4px;
|
|
}
|
|
.media-provider-reload-btn {
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
padding: 4px 8px;
|
|
gap: 4px;
|
|
}
|
|
.media-provider-reload-btn:hover {
|
|
color: var(--text);
|
|
}
|
|
.settings-about-diagnostics {
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 16px;
|
|
padding: 12px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.settings-about-diagnostics-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
}
|
|
.settings-about-diagnostics h4 {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
}
|
|
.settings-about-diagnostics-text .hint {
|
|
margin: 0;
|
|
font-size: 12px;
|
|
}
|
|
.diagnostics-export-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
gap: 6px;
|
|
flex: 0 0 auto;
|
|
max-width: 220px;
|
|
}
|
|
.diagnostics-export-button {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
white-space: nowrap;
|
|
}
|
|
.diagnostics-export-status {
|
|
margin: 0;
|
|
font-size: 12px;
|
|
overflow-wrap: anywhere;
|
|
text-align: right;
|
|
}
|
|
.diagnostics-export-status.success { color: var(--text); }
|
|
.diagnostics-export-status.error { color: var(--danger, #d4543b); }
|
|
|
|
.media-provider-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.media-provider-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.media-provider-row.pending {
|
|
background: var(--bg-subtle);
|
|
border-style: dashed;
|
|
}
|
|
.media-provider-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
}
|
|
.media-provider-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.media-provider-name-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.media-provider-name {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
}
|
|
.media-provider-hint {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
}
|
|
.media-provider-badge {
|
|
align-self: flex-start;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 2px 7px;
|
|
border-radius: var(--radius-pill);
|
|
border: 1px solid var(--border);
|
|
}
|
|
.media-provider-badges {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: flex-end;
|
|
gap: 6px;
|
|
}
|
|
.media-provider-badge.integrated {
|
|
color: #137a3d;
|
|
background: color-mix(in srgb, #1f9d55 10%, transparent);
|
|
border-color: color-mix(in srgb, #1f9d55 28%, var(--border));
|
|
}
|
|
.media-provider-badge.unsupported {
|
|
color: var(--text-soft);
|
|
background: var(--bg-subtle);
|
|
border-color: var(--border);
|
|
}
|
|
.media-provider-badge.on {
|
|
color: #3155c9;
|
|
background: color-mix(in srgb, #4169e1 10%, transparent);
|
|
border-color: color-mix(in srgb, #4169e1 28%, var(--border));
|
|
}
|
|
.media-provider-body {
|
|
display: grid;
|
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
|
|
gap: 6px;
|
|
}
|
|
/*
|
|
* "Coming soon" drawer under the media provider list. We render the
|
|
* roadmap providers as a denser, read-only list rather than the same
|
|
* editable cards as the main list — the user can't actually configure
|
|
* these, so giving them the same visual weight is misleading. The
|
|
* <details>/<summary> is styled by the shared .memory-details-summary
|
|
* rules; only the inner list needs new styles.
|
|
*/
|
|
.media-provider-coming-soon {
|
|
margin-top: 12px;
|
|
padding: 10px 12px;
|
|
background: var(--bg-subtle);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.media-provider-coming-soon[open] {
|
|
padding-bottom: 12px;
|
|
}
|
|
.media-provider-coming-soon-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.media-provider-coming-soon-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 8px 0;
|
|
border-top: 1px solid var(--border-soft, var(--border));
|
|
}
|
|
.media-provider-coming-soon-item:first-child {
|
|
border-top: 0;
|
|
}
|
|
.media-provider-coming-soon-meta {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.media-provider-secret-field {
|
|
position: relative;
|
|
min-width: 0;
|
|
}
|
|
.media-provider-secret-field input {
|
|
width: 100%;
|
|
padding-right: 34px;
|
|
}
|
|
.secret-visibility-button {
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 8px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 22px;
|
|
height: 22px;
|
|
padding: 0;
|
|
color: var(--text-muted);
|
|
background: transparent;
|
|
border: 0;
|
|
border-radius: var(--radius-pill);
|
|
box-shadow: none;
|
|
transform: translateY(-50%);
|
|
}
|
|
.secret-visibility-button:hover:not(:disabled) {
|
|
color: var(--text);
|
|
background: var(--bg-subtle);
|
|
box-shadow: none;
|
|
}
|
|
.secret-visibility-button:disabled {
|
|
color: var(--text-faint);
|
|
cursor: not-allowed;
|
|
}
|
|
.section-head {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
.section-head > div {
|
|
max-width: 100%;
|
|
min-width: 0;
|
|
}
|
|
/*
|
|
Section-head action buttons (Reload, Test, Rescan, Fetch models, …)
|
|
must read on a single line. Without an explicit nowrap they wrap when
|
|
the section title + hint take most of the row, producing label
|
|
fragments like "Reload from / daemon" that look broken. flex-shrink:0
|
|
keeps the button at its content width even when the description gets
|
|
long; long button labels are then guarded by their own copy budget.
|
|
*/
|
|
.section-head > button,
|
|
.section-head .section-head-actions > button {
|
|
white-space: nowrap;
|
|
flex-shrink: 0;
|
|
}
|
|
.section-head h3 { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0.01em; }
|
|
.section-head .hint { margin-top: 2px; }
|
|
.field { display: flex; flex-direction: column; gap: 4px; }
|
|
.field-label { font-size: 12px; font-weight: 500; color: var(--text-muted); }
|
|
.field-required {
|
|
margin-left: 4px;
|
|
color: var(--red);
|
|
font-weight: 700;
|
|
}
|
|
.field-error,
|
|
.field-inline-status {
|
|
font-size: 11.5px;
|
|
line-height: 1.35;
|
|
}
|
|
.field-error {
|
|
color: var(--red);
|
|
}
|
|
.field-inline-status.running {
|
|
color: var(--accent-strong);
|
|
}
|
|
.field-inline-status.success {
|
|
color: var(--green);
|
|
}
|
|
.field-row { display: flex; gap: 6px; align-items: stretch; }
|
|
.field-row input { flex: 1; }
|
|
.field-row .icon-btn { white-space: nowrap; padding: 6px 12px; }
|
|
.settings-base-url-readonly .field-row input[readonly] {
|
|
color: var(--text-muted);
|
|
cursor: default;
|
|
}
|
|
.settings-base-url-customize {
|
|
flex-shrink: 0;
|
|
}
|
|
.settings-language-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.settings-language-tile {
|
|
display: grid;
|
|
grid-template-columns: 1fr auto;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 12px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text);
|
|
text-align: left;
|
|
box-shadow: none;
|
|
min-width: 0;
|
|
}
|
|
.settings-language-tile-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.settings-language-tile-title {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.settings-language-tile-code {
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0.02em;
|
|
}
|
|
.settings-language-tile:hover {
|
|
border-color: var(--border-strong);
|
|
background: var(--bg-subtle);
|
|
}
|
|
/* Differentiate hover, selected, and keyboard-focus states so the
|
|
currently-selected language is visually distinct even when the
|
|
pointer is hovering a different tile. Issue #628 (carries over from
|
|
the previous dropdown form). */
|
|
.settings-language-tile.active {
|
|
background: var(--accent-tint);
|
|
border-color: var(--accent);
|
|
color: var(--accent);
|
|
font-weight: 500;
|
|
}
|
|
.settings-language-tile.active .settings-language-tile-title { color: var(--accent); }
|
|
.settings-language-tile.active:hover { background: var(--accent-soft); }
|
|
.settings-language-tile:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: -2px;
|
|
}
|
|
.empty-card {
|
|
padding: 16px;
|
|
border: 1px dashed var(--border);
|
|
border-radius: var(--radius-sm);
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
background: var(--bg-subtle);
|
|
}
|
|
|
|
.agent-scan-card {
|
|
position: relative;
|
|
display: grid;
|
|
align-content: center;
|
|
gap: 20px;
|
|
overflow: hidden;
|
|
min-height: 296px;
|
|
padding: 30px 34px;
|
|
border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--border));
|
|
border-radius: 14px;
|
|
background:
|
|
radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--accent) 13%, transparent), transparent 34%),
|
|
linear-gradient(180deg, color-mix(in srgb, var(--bg-panel) 90%, var(--accent) 10%), var(--bg-subtle));
|
|
box-shadow:
|
|
inset 0 1px 0 color-mix(in srgb, #fff 6%, transparent),
|
|
0 18px 42px color-mix(in srgb, #000 18%, transparent);
|
|
}
|
|
|
|
.agent-scan-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 18px;
|
|
border-radius: 12px;
|
|
border: 1px solid color-mix(in srgb, var(--accent) 8%, transparent);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.agent-scan-card__stage {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
text-align: center;
|
|
}
|
|
|
|
.agent-scan-card__ring {
|
|
position: relative;
|
|
display: block;
|
|
width: 46px;
|
|
height: 46px;
|
|
margin-bottom: 14px;
|
|
border-radius: 999px;
|
|
background:
|
|
radial-gradient(circle at center, var(--bg-subtle) 50%, transparent 52%),
|
|
conic-gradient(from 0deg, color-mix(in srgb, var(--accent) 92%, #fff) 0 84deg, color-mix(in srgb, var(--accent) 22%, transparent) 84deg 180deg, transparent 180deg 360deg);
|
|
box-shadow:
|
|
0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent),
|
|
0 0 32px color-mix(in srgb, var(--accent) 18%, transparent);
|
|
animation: agent-scan-ring 1.15s linear infinite;
|
|
}
|
|
|
|
.agent-scan-card__ring::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 15px;
|
|
border-radius: inherit;
|
|
background: var(--accent);
|
|
box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 42%, transparent);
|
|
}
|
|
|
|
.agent-scan-card__stage strong {
|
|
color: var(--text);
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
line-height: 1.25;
|
|
}
|
|
|
|
.agent-scan-card__stage > span:not(.agent-scan-card__ring) {
|
|
margin-top: 6px;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.agent-scan-card__progress {
|
|
width: min(240px, 70%);
|
|
height: 2px;
|
|
margin-top: 18px;
|
|
overflow: hidden;
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--text) 8%, transparent);
|
|
}
|
|
|
|
.agent-scan-card__progress span {
|
|
display: block;
|
|
width: 44%;
|
|
height: 100%;
|
|
border-radius: inherit;
|
|
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
|
animation: agent-scan-progress 1.35s cubic-bezier(0.23, 1, 0.32, 1) infinite;
|
|
}
|
|
|
|
.agent-scan-card__rows {
|
|
position: relative;
|
|
z-index: 1;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
gap: 10px;
|
|
width: min(640px, 100%);
|
|
justify-self: center;
|
|
}
|
|
|
|
.agent-scan-card__rows span {
|
|
display: grid;
|
|
grid-template-columns: 28px minmax(0, 1fr);
|
|
grid-template-rows: 10px 8px;
|
|
gap: 8px 10px;
|
|
align-items: center;
|
|
min-height: 54px;
|
|
padding: 12px;
|
|
border: 1px solid color-mix(in srgb, var(--border) 72%, transparent);
|
|
border-radius: 10px;
|
|
background: color-mix(in srgb, var(--bg-panel) 72%, transparent);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.agent-scan-card__rows i,
|
|
.agent-scan-card__rows b,
|
|
.agent-scan-card__rows em {
|
|
display: block;
|
|
overflow: hidden;
|
|
border-radius: 999px;
|
|
background:
|
|
linear-gradient(90deg, transparent, color-mix(in srgb, var(--text) 9%, transparent), transparent),
|
|
color-mix(in srgb, var(--text) 9%, transparent);
|
|
background-size: 220% 100%, 100% 100%;
|
|
animation: agent-scan-row 1.45s cubic-bezier(0.23, 1, 0.32, 1) infinite;
|
|
}
|
|
|
|
.agent-scan-card__rows i {
|
|
grid-row: 1 / span 2;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.agent-scan-card__rows b {
|
|
width: 82%;
|
|
height: 10px;
|
|
}
|
|
|
|
.agent-scan-card__rows em {
|
|
width: 54%;
|
|
height: 8px;
|
|
}
|
|
|
|
.agent-scan-card__rows span:nth-child(2) {
|
|
opacity: 0.58;
|
|
}
|
|
|
|
.agent-scan-card__rows span:nth-child(3) {
|
|
opacity: 0.46;
|
|
}
|
|
|
|
.agent-scan-card__rows span:nth-child(2) i,
|
|
.agent-scan-card__rows span:nth-child(2) b,
|
|
.agent-scan-card__rows span:nth-child(2) em {
|
|
animation-delay: 120ms;
|
|
}
|
|
|
|
.agent-scan-card__rows span:nth-child(3) i,
|
|
.agent-scan-card__rows span:nth-child(3) b,
|
|
.agent-scan-card__rows span:nth-child(3) em {
|
|
animation-delay: 240ms;
|
|
}
|
|
|
|
@keyframes agent-scan-ring {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
@keyframes agent-scan-progress {
|
|
from {
|
|
transform: translateX(-100%);
|
|
}
|
|
to {
|
|
transform: translateX(230%);
|
|
}
|
|
}
|
|
|
|
@keyframes agent-scan-row {
|
|
from {
|
|
background-position: 120% 0, 0 0;
|
|
}
|
|
to {
|
|
background-position: -120% 0, 0 0;
|
|
}
|
|
}
|
|
|
|
.agent-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 8px;
|
|
}
|
|
.agent-grid-installed {
|
|
grid-template-columns: minmax(0, 1fr);
|
|
}
|
|
.agent-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.agent-group-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
}
|
|
.agent-group-head h4 {
|
|
margin: 0;
|
|
font-size: 12px;
|
|
font-weight: 650;
|
|
color: var(--text);
|
|
}
|
|
.agent-group-head-actions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
min-width: 0;
|
|
}
|
|
.agent-group-rescan-btn {
|
|
min-width: 92px;
|
|
min-height: 32px;
|
|
}
|
|
.agent-install-collapse {
|
|
margin-top: 10px;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-subtle);
|
|
}
|
|
.agent-install-collapse-summary {
|
|
list-style: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
user-select: none;
|
|
}
|
|
.agent-install-collapse-summary::-webkit-details-marker {
|
|
display: none;
|
|
}
|
|
.agent-install-collapse-summary::before {
|
|
content: '';
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 5px solid currentColor;
|
|
border-top: 4px solid transparent;
|
|
border-bottom: 4px solid transparent;
|
|
transition: transform 0.15s ease;
|
|
}
|
|
.agent-install-collapse[open] > .agent-install-collapse-summary::before {
|
|
transform: rotate(90deg);
|
|
}
|
|
.agent-grid-unavailable {
|
|
margin-top: 10px;
|
|
}
|
|
/* Test feedback row injected into the agent grid right after the
|
|
tested/selected card. Spans both columns so the result reads as a
|
|
continuation of that card's row, instead of a top-of-section banner
|
|
that pushes the grid down on every Test/Rescan click. */
|
|
.agent-test-result-row {
|
|
grid-column: 1 / -1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.agent-test-result-row > .settings-test-status {
|
|
margin: 0;
|
|
}
|
|
.agent-test-result-row > .settings-test-actions {
|
|
margin-top: 0;
|
|
}
|
|
.agent-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
/* Uniform height regardless of state: an installed agent only renders
|
|
name + version, while an unavailable one adds an Install/Docs row.
|
|
Without `min-height` the two variants render ~50px vs ~68px and the
|
|
grid looks ragged. Pinning to ~64px lets installed cards center their
|
|
two-line body in the same box that the action variant fills. */
|
|
min-height: 64px;
|
|
padding: 6px 10px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
position: relative;
|
|
transition: border-color 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
|
}
|
|
.agent-card-installed {
|
|
display: block;
|
|
padding: 0;
|
|
min-height: 72px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Failed-run AMR nudge: when the chat error card's "Try Open Design AMR" link
|
|
opens Settings, the AMR card scrolls into view and pulses a couple of times.
|
|
The card's own box-shadow is not clipped by `overflow: hidden`, so the glow
|
|
reads fully even though inner content is clipped. */
|
|
.agent-card--amr-highlight {
|
|
border-color: var(--accent);
|
|
animation: amr-card-pulse 1s cubic-bezier(0.23, 1, 0.32, 1) 2;
|
|
}
|
|
@keyframes amr-card-pulse {
|
|
0%,
|
|
100% {
|
|
box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent);
|
|
}
|
|
35% {
|
|
box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 34%, transparent);
|
|
}
|
|
}
|
|
|
|
/* Authorize-button coachmark: a fake cursor that fades + slides in from the
|
|
lower-right, landing with its tip on the AMR authorize button, inside a
|
|
pulsing ring. Anchored to the button (`.amr-auth-anchor`) so it always lines
|
|
up. `pointer-events: none` keeps the real button clickable; it dismisses the
|
|
moment the real pointer reaches the button (handled in JS). */
|
|
.amr-auth-anchor {
|
|
position: relative;
|
|
display: inline-flex;
|
|
}
|
|
.amr-coachmark {
|
|
position: absolute;
|
|
/* Horizontally centered on the button, sitting low — the hand's fingertip
|
|
(top of the glyph) then points up into the button. `margin-left` centers
|
|
the box at `left: 50%` so `transform` stays free for the entrance. */
|
|
left: 50%;
|
|
margin-left: -12px;
|
|
bottom: 7px;
|
|
width: 24px;
|
|
height: 24px;
|
|
pointer-events: none;
|
|
z-index: 4;
|
|
animation: amr-coachmark-in 460ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
|
}
|
|
/* Fade + rise straight up into place (bottom → up). */
|
|
@keyframes amr-coachmark-in {
|
|
0% {
|
|
opacity: 0;
|
|
transform: translateY(16px) scale(0.8);
|
|
}
|
|
100% {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
.amr-coachmark__ring {
|
|
position: absolute;
|
|
inset: -3px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--accent);
|
|
opacity: 0;
|
|
/* Start pulsing only after the cursor has settled in. */
|
|
animation: amr-coachmark-ring 1.5s cubic-bezier(0.23, 1, 0.32, 1) 460ms infinite;
|
|
}
|
|
@keyframes amr-coachmark-ring {
|
|
0% {
|
|
transform: scale(0.55);
|
|
opacity: 0;
|
|
}
|
|
30% {
|
|
opacity: 0.65;
|
|
}
|
|
100% {
|
|
transform: scale(1.25);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.amr-coachmark__cursor {
|
|
position: absolute;
|
|
inset: 0;
|
|
filter: drop-shadow(0 1px 2px color-mix(in srgb, var(--text-muted) 45%, transparent));
|
|
}
|
|
.agent-card-main {
|
|
display: flex;
|
|
align-items: stretch;
|
|
min-width: 0;
|
|
}
|
|
.agent-card-select {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 70px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 14px 10px 18px;
|
|
border: 0;
|
|
background: transparent;
|
|
color: inherit;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
}
|
|
.agent-card-test-btn {
|
|
align-self: stretch;
|
|
flex-shrink: 0;
|
|
min-width: 112px;
|
|
margin: 0;
|
|
padding: 0 18px;
|
|
border: 0;
|
|
border-left: 1px solid var(--border-soft);
|
|
border-radius: 0;
|
|
background: transparent;
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
}
|
|
.agent-card-test-btn:hover:not(:disabled) {
|
|
background: color-mix(in srgb, var(--accent-tint) 58%, transparent);
|
|
color: var(--accent-strong);
|
|
}
|
|
.agent-card:hover:not(.disabled) {
|
|
border-color: var(--border-strong);
|
|
}
|
|
.agent-card.active {
|
|
border-color: color-mix(in srgb, var(--accent) 36%, var(--border));
|
|
background: var(--bg-panel);
|
|
box-shadow:
|
|
0 1px 0 color-mix(in srgb, var(--bg-panel) 88%, transparent) inset,
|
|
0 10px 28px color-mix(in srgb, var(--text) 9%, transparent);
|
|
}
|
|
.agent-card.active::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 12px auto 12px 0;
|
|
width: 4px;
|
|
border-radius: 0 999px 999px 0;
|
|
background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 56%, var(--selected)));
|
|
}
|
|
.agent-card-installed.active .agent-card-main {
|
|
background:
|
|
linear-gradient(
|
|
180deg,
|
|
color-mix(in srgb, var(--accent-tint) 32%, var(--bg-panel)),
|
|
color-mix(in srgb, var(--accent-tint) 10%, var(--bg-panel))
|
|
);
|
|
}
|
|
.agent-card-installed.active .agent-card-select {
|
|
min-height: 78px;
|
|
padding: 14px 18px 12px 22px;
|
|
}
|
|
.agent-card-installed.active .agent-icon {
|
|
border-radius: 11px;
|
|
box-shadow:
|
|
0 0 0 1px color-mix(in srgb, var(--accent) 16%, var(--border-soft)),
|
|
0 8px 18px color-mix(in srgb, var(--accent) 16%, transparent);
|
|
}
|
|
.agent-card-installed.active .agent-card-name {
|
|
font-size: 13px;
|
|
}
|
|
.agent-card-installed.active .agent-card-meta {
|
|
margin-top: 1px;
|
|
}
|
|
.agent-card.disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.55;
|
|
}
|
|
.agent-card.disabled.agent-card-unavailable {
|
|
opacity: 0.72;
|
|
}
|
|
.agent-card-actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px 10px;
|
|
margin-top: 3px;
|
|
}
|
|
/* Inline variant: links sit to the right of the agent name as a sibling
|
|
of .agent-card-body, so unavailable cards collapse to a single row
|
|
(no "not installed" label, no version meta) and the long tail of
|
|
unavailable adapters shrinks. */
|
|
.agent-card-actions--inline {
|
|
margin-top: 0;
|
|
flex: 0 0 auto;
|
|
flex-wrap: nowrap;
|
|
align-items: center;
|
|
}
|
|
.agent-card-link {
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
color: var(--text-muted);
|
|
text-decoration: none;
|
|
transition: color 120ms ease;
|
|
}
|
|
.agent-card-link:hover {
|
|
color: var(--text);
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
}
|
|
/* Docs link: secondary affordance, plain muted text. Sits to the left
|
|
of the Install ghost button so the primary "do this" action keeps the
|
|
right edge. Hover toggles the underline only — no background, no
|
|
color change — to keep visual weight low. */
|
|
.agent-card-link--muted {
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
}
|
|
.agent-card-link--muted:hover {
|
|
color: var(--text);
|
|
}
|
|
/* Install link: primary affordance for an unavailable card, styled as
|
|
a ghost button (transparent + outlined) so it reads as a real
|
|
actionable target rather than yet another text link. */
|
|
.agent-card-link--ghost {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 3px 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: transparent;
|
|
color: var(--text);
|
|
}
|
|
.agent-card-link--ghost:hover {
|
|
background: var(--bg-subtle);
|
|
border-color: var(--border-strong);
|
|
text-decoration: none;
|
|
}
|
|
.agent-install-path-hint {
|
|
margin-top: 10px;
|
|
margin-bottom: 0;
|
|
}
|
|
.agent-install-guide {
|
|
margin-top: 10px;
|
|
}
|
|
.agent-install-steps {
|
|
margin: 8px 0 0;
|
|
padding-left: 18px;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
line-height: 1.45;
|
|
}
|
|
.agent-install-steps li + li {
|
|
margin-top: 4px;
|
|
}
|
|
.agent-card-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
|
|
.agent-card-name {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 5px;
|
|
min-width: 0;
|
|
font-size: 12.5px;
|
|
font-weight: 650;
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
.agent-card-name > span:first-child,
|
|
.agent-card-tagline {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-name > span:first-child {
|
|
flex: 0 1 auto;
|
|
}
|
|
.agent-card-title {
|
|
min-width: 0;
|
|
}
|
|
.agent-card-name--amr {
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.agent-card-name--amr .agent-card-title {
|
|
flex: 0 0 auto;
|
|
}
|
|
.agent-card-benefits {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
}
|
|
.agent-card-benefit {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex: 0 0 auto;
|
|
min-height: 20px;
|
|
padding: 0 7px;
|
|
border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--border-soft));
|
|
border-radius: 999px;
|
|
background: color-mix(in srgb, var(--accent) 8%, var(--bg-panel));
|
|
color: color-mix(in srgb, var(--accent-strong) 82%, var(--text-strong));
|
|
font-size: 10.5px;
|
|
font-weight: 680;
|
|
line-height: 1;
|
|
white-space: nowrap;
|
|
}
|
|
.agent-card-name-divider,
|
|
.agent-card-tagline {
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
}
|
|
.agent-card-tagline {
|
|
flex: 1 1 auto;
|
|
}
|
|
.agent-card-description {
|
|
margin-top: 1px;
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-meta {
|
|
font-size: 11px; color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
line-height: 1.35;
|
|
}
|
|
.agent-card-meta .muted { color: var(--text-soft); font-style: italic; }
|
|
.agent-card-amr-email {
|
|
margin-top: 4px;
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
font-variant-numeric: tabular-nums;
|
|
line-height: 1.35;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-amr-email span {
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-model-summary {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 6px;
|
|
margin-top: 4px;
|
|
min-width: 0;
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
line-height: 1.3;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-model-summary span {
|
|
flex: 0 0 auto;
|
|
}
|
|
.agent-card-model-summary strong {
|
|
min-width: 0;
|
|
color: var(--text);
|
|
font-weight: 600;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.agent-card-amr-auth {
|
|
align-self: stretch;
|
|
flex: 0 1 auto;
|
|
min-width: 112px;
|
|
max-width: 280px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-left: 1px solid var(--border-soft);
|
|
}
|
|
.agent-card-amr-auth--placeholder {
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
}
|
|
.agent-card-amr-auth .amr-account-control {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 0;
|
|
gap: 8px;
|
|
padding: 0 14px;
|
|
}
|
|
.agent-card-amr-auth .amr-account-control__status {
|
|
max-width: 150px;
|
|
overflow: hidden;
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.agent-card-amr-auth .amr-account-control__action {
|
|
min-height: 36px;
|
|
padding: 0 18px;
|
|
border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
|
|
border-radius: 10px;
|
|
background: color-mix(in srgb, var(--accent-tint) 62%, var(--bg-panel));
|
|
color: var(--text);
|
|
font-size: 13px;
|
|
font-weight: 680;
|
|
cursor: pointer;
|
|
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, transform 120ms ease;
|
|
}
|
|
.agent-card-amr-auth .amr-account-control__action:hover:not(:disabled) {
|
|
border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
|
|
background: color-mix(in srgb, var(--accent-tint) 76%, var(--bg-panel));
|
|
color: var(--accent-strong);
|
|
transform: translateY(-1px);
|
|
}
|
|
.agent-card-amr-auth .amr-account-control__action:disabled {
|
|
cursor: default;
|
|
opacity: 0.62;
|
|
}
|
|
.agent-card-amr-auth .amr-account-control__error,
|
|
.agent-card-amr-auth .amr-login-pill-badge {
|
|
display: none;
|
|
}
|
|
.agent-model-row-head {
|
|
font-size: 12px;
|
|
color: var(--text);
|
|
}
|
|
.agent-model-row-head strong {
|
|
font-weight: 600;
|
|
}
|
|
.agent-model-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
padding: 12px;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-panel);
|
|
}
|
|
.agent-card-config {
|
|
display: grid;
|
|
gap: 9px;
|
|
padding: 16px 24px 18px;
|
|
border-top: 1px solid color-mix(in srgb, var(--accent) 14%, var(--border-soft));
|
|
background:
|
|
linear-gradient(
|
|
180deg,
|
|
color-mix(in srgb, var(--bg-subtle) 52%, var(--bg-panel)),
|
|
color-mix(in srgb, var(--bg-panel) 92%, var(--bg-subtle))
|
|
);
|
|
}
|
|
.agent-card-config select,
|
|
.agent-card-config input,
|
|
.agent-model-row select,
|
|
.agent-model-row input,
|
|
.agent-cli-env select,
|
|
.agent-cli-env input,
|
|
.settings-section-byok select,
|
|
.settings-section-byok input {
|
|
background-color: var(--bg-subtle);
|
|
}
|
|
/*
|
|
Generic Settings card — wraps a single panel of related controls in a
|
|
white-on-soft-border container so users get an obvious "this lump of
|
|
fields belongs together" boundary instead of a long flat scroll. Used
|
|
by the BYOK form, the Custom instructions block, the Memory list, and
|
|
any future Settings section that should read as a card.
|
|
*/
|
|
.settings-section-card {
|
|
padding: 16px;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-panel);
|
|
}
|
|
.agent-card-config .field,
|
|
.agent-model-row .field { gap: 4px; }
|
|
.agent-card-config .field-label,
|
|
.agent-model-row .field-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
font-size: 11.5px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: var(--text-muted);
|
|
}
|
|
.agent-card-config .field-label {
|
|
margin-bottom: 2px;
|
|
font-size: 11px;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
.agent-model-source-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
min-height: 18px;
|
|
padding: 2px 6px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
background: var(--bg-subtle);
|
|
color: var(--text-muted);
|
|
font-size: 10.5px;
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
letter-spacing: 0;
|
|
text-transform: none;
|
|
}
|
|
.agent-model-source-badge.live {
|
|
border-color: var(--green-border);
|
|
background: var(--green-bg);
|
|
color: var(--green);
|
|
}
|
|
.agent-model-source-badge.fallback {
|
|
min-height: auto;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
}
|
|
.agent-model-source-badge.fallback::before {
|
|
content: '·';
|
|
margin-right: 6px;
|
|
color: var(--text-muted);
|
|
}
|
|
.agent-card-config .hint,
|
|
.agent-model-row .hint { margin: 0; font-size: 11.5px; }
|
|
.agent-card-config .hint {
|
|
color: var(--text-muted);
|
|
line-height: 1.35;
|
|
}
|
|
.agent-model-select-wrap {
|
|
position: relative;
|
|
}
|
|
.agent-model-select-wrap select {
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
background-image: none;
|
|
padding-right: 28px;
|
|
width: 100%;
|
|
}
|
|
.agent-card-config .agent-model-select-wrap select {
|
|
min-height: 36px;
|
|
border-color: color-mix(in srgb, var(--border) 78%, transparent);
|
|
background: color-mix(in srgb, var(--bg-subtle) 82%, var(--bg-panel));
|
|
}
|
|
.agent-card-config .agent-model-select-wrap select:hover {
|
|
border-color: var(--border-strong);
|
|
}
|
|
.agent-model-select-chevron {
|
|
position: absolute;
|
|
right: 8px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-soft);
|
|
pointer-events: none;
|
|
}
|
|
.agent-cli-env {
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border-soft);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-panel);
|
|
}
|
|
.agent-cli-env-summary {
|
|
list-style: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
user-select: none;
|
|
}
|
|
.agent-cli-env-summary::-webkit-details-marker {
|
|
display: none;
|
|
}
|
|
.agent-cli-env-summary::before {
|
|
content: '';
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 5px solid currentColor;
|
|
border-top: 4px solid transparent;
|
|
border-bottom: 4px solid transparent;
|
|
color: var(--text-muted);
|
|
transition: transform 0.15s ease;
|
|
}
|
|
.agent-cli-env[open] > .agent-cli-env-summary::before {
|
|
transform: rotate(90deg);
|
|
}
|
|
.agent-cli-env-summary-title {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
}
|
|
.agent-cli-env-body {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
.agent-cli-env-body .hint {
|
|
margin: 0;
|
|
font-size: 11.5px;
|
|
}
|
|
.agent-cli-env-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.status-dot {
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: #cbd5e1;
|
|
flex-shrink: 0;
|
|
}
|
|
.status-dot.active {
|
|
background: var(--accent);
|
|
box-shadow: 0 0 0 3px var(--accent-soft);
|
|
}
|
|
/* Flat brand glyph rendered single-color via currentColor — no surrounding
|
|
colored block. Brands that ship with simple-icons get their official
|
|
mark; the rest fall back to .agent-icon-fallback below. */
|
|
.agent-icon {
|
|
flex-shrink: 0;
|
|
color: var(--text);
|
|
}
|
|
.agent-icon-fallback {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--bg-subtle);
|
|
color: var(--text-muted);
|
|
border-radius: 6px;
|
|
font-weight: 600;
|
|
font-family: var(--font-mono, monospace);
|
|
letter-spacing: 0;
|
|
}
|
|
/* Theme-aware rendering for monochrome brand SVGs whose paths use
|
|
`fill="currentColor"`. Loaded through `<img>` they'd resolve to the
|
|
SVG document's default (black) and disappear against dark surfaces.
|
|
Painting them via a CSS mask lets `background-color: currentColor`
|
|
inherit the surrounding text color, so the same mark stays legible
|
|
under light + dark themes. */
|
|
.agent-icon-mono {
|
|
display: inline-block;
|
|
background-color: currentColor;
|
|
-webkit-mask-size: contain;
|
|
mask-size: contain;
|
|
-webkit-mask-repeat: no-repeat;
|
|
mask-repeat: no-repeat;
|
|
-webkit-mask-position: center;
|
|
mask-position: center;
|
|
}
|
|
|
|
.error {
|
|
border-color: var(--red-border);
|
|
background: var(--red-bg);
|
|
color: var(--red);
|
|
}
|
|
|
|
/* ===========================================================
|
|
Activity ticker (legacy — still used in some flows)
|
|
=========================================================== */
|
|
.activity {
|
|
margin: 4px 0 8px;
|
|
padding: 8px 10px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-subtle);
|
|
}
|
|
.activity-header { display: flex; align-items: center; gap: 8px; font-size: 12px; margin-bottom: 6px; }
|
|
.activity-header .dot {
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%;
|
|
background: var(--text-muted);
|
|
flex: 0 0 auto;
|
|
}
|
|
.activity-header .dot[data-active="true"] {
|
|
background: var(--accent);
|
|
animation: pulse 1.2s ease-in-out infinite;
|
|
}
|
|
.activity-title { font-weight: 500; }
|
|
.activity-stats {
|
|
margin-left: auto;
|
|
color: var(--text-muted);
|
|
font-variant-numeric: tabular-nums;
|
|
font-size: 11px;
|
|
}
|
|
.activity-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
max-height: 280px;
|
|
overflow-y: auto;
|
|
}
|
|
.activity-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
padding: 2px 0;
|
|
}
|
|
.activity-item .badge {
|
|
flex: 0 0 auto;
|
|
display: inline-block;
|
|
padding: 1px 6px;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.02em;
|
|
background: var(--bg-subtle);
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--border);
|
|
min-width: 56px;
|
|
text-align: center;
|
|
}
|
|
.activity-item .badge-tool { background: var(--blue-bg); border-color: var(--blue-border); color: var(--blue); }
|
|
.activity-item .badge-result { background: var(--green-bg); border-color: var(--green-border); color: var(--green); }
|
|
.activity-item .badge-error,
|
|
.activity-item .badge-result.badge-error {
|
|
background: var(--red-bg); border-color: var(--red-border); color: var(--red);
|
|
}
|
|
.activity-item .badge-thinking { background: var(--purple-bg); border-color: var(--purple-border); color: var(--purple); }
|
|
.activity-item .badge-status { background: var(--bg-panel); }
|
|
.activity-item .badge-text { background: transparent; border-color: var(--border); }
|
|
.activity-item .badge-usage { background: var(--bg-panel); color: var(--text-muted); }
|
|
.activity-item .detail { flex: 1; min-width: 0; overflow-wrap: break-word; word-break: break-word; }
|
|
.activity-item .detail.muted { color: var(--text-muted); }
|
|
.activity-item .thinking-text { font-style: italic; color: var(--text-muted); }
|
|
.activity-waiting { font-size: 11px; color: var(--text-muted); font-style: italic; }
|
|
|
|
/* ============================================================
|
|
Entry view — left sidebar + right tabs
|
|
============================================================ */
|
|
.entry-shell {
|
|
display: grid;
|
|
grid-template-rows: auto minmax(0, 1fr);
|
|
height: 100vh;
|
|
min-height: 0;
|
|
background: var(--bg);
|
|
}
|
|
/* Note: `.entry-shell--no-header` and the rest of the entry-view
|
|
redesign live in `src/styles/home/`. Keeping that bundle out of
|
|
this file makes upstream `index.css` rebases easier. */
|
|
|
|
.entry {
|
|
display: grid;
|
|
grid-template-columns: var(--entry-rail-width, 64px) 1fr;
|
|
height: 100%;
|
|
min-height: 0;
|
|
background: var(--bg);
|
|
}
|
|
|
|
.entry-side {
|
|
border-right: 1px solid var(--border);
|
|
background: transparent;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* ============================================================
|
|
NEW PROJECT PANEL — design standards
|
|
This block applies to every tab inside `.newproj` (prototype,
|
|
live-artifact, slide deck, template, media, other). Pickers and
|
|
options added to new tabs should slot into these rules instead
|
|
of inventing local variants.
|
|
|
|
Heights
|
|
text input ............ 30px (.newproj-name, .newproj-folder-input)
|
|
dropdown trigger ...... 38px (.newproj-section .ds-picker-trigger)
|
|
compact toggle ........ 36px (.compact-toggle)
|
|
popover item .......... 38px (.ds-picker-item)
|
|
|
|
Color rules — neutral only. The orange accent is reserved for
|
|
the Create CTA. Nothing else inside `.newproj` should pull from
|
|
`var(--accent)` / `--accent-tint` / `--accent-soft`.
|
|
hover bg .............. var(--bg-subtle)
|
|
focused input ......... border var(--text) + 8% black halo
|
|
(.entry-side input:focus)
|
|
active border ......... var(--border-strong)
|
|
active mark ........... var(--text)
|
|
segmented control ..... mirror .subtab-pill (gray rounded
|
|
container + white raised active button)
|
|
============================================================ */
|
|
.newproj {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
padding-top: 24px;
|
|
}
|
|
.newproj-tabs-shell {
|
|
position: relative;
|
|
overflow: visible;
|
|
padding: 0 24px;
|
|
margin-bottom: -1px;
|
|
z-index: 2;
|
|
flex: 0 0 auto;
|
|
}
|
|
.newproj-tabs {
|
|
display: flex;
|
|
padding: 0;
|
|
gap: 2px;
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
scroll-behavior: smooth;
|
|
scrollbar-width: none;
|
|
}
|
|
.newproj-tabs::-webkit-scrollbar { display: none; }
|
|
.newproj-tabs-shell::before,
|
|
.newproj-tabs-shell::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 28px;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 120ms ease;
|
|
}
|
|
.newproj-tabs-shell::before {
|
|
left: 0;
|
|
background: linear-gradient(90deg, var(--bg), transparent);
|
|
}
|
|
.newproj-tabs-shell::after {
|
|
right: 0;
|
|
background: linear-gradient(270deg, var(--bg), transparent);
|
|
}
|
|
.newproj-tabs-shell.can-left::before,
|
|
.newproj-tabs-shell.can-right::after {
|
|
opacity: 1;
|
|
}
|
|
.newproj-tabs-arrow {
|
|
display: none;
|
|
position: absolute;
|
|
top: 50%;
|
|
z-index: 2;
|
|
width: 28px;
|
|
height: 28px;
|
|
transform: translateY(-50%);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
background: var(--bg-panel);
|
|
color: var(--text-strong);
|
|
box-shadow: var(--shadow-xs);
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0;
|
|
transition: opacity 120ms ease, transform 120ms ease;
|
|
}
|
|
.newproj-tabs-arrow:hover { border-color: var(--border-strong); background: var(--bg-subtle); }
|
|
.newproj-tabs-arrow svg {
|
|
display: block;
|
|
flex: none;
|
|
}
|
|
.newproj-tabs-arrow.left { left: 6px; }
|
|
.newproj-tabs-arrow.right { right: 6px; }
|
|
.newproj-tabs-arrow.hidden {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transform: translateY(-50%) scale(0.92);
|
|
}
|
|
.newproj-tab {
|
|
flex: 0 0 auto;
|
|
min-width: max-content;
|
|
padding: 7px 14px;
|
|
background: transparent;
|
|
font-size: 12px;
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
border: 1px solid transparent;
|
|
border-radius: 12px 12px 0 0;
|
|
position: relative;
|
|
transition: none;
|
|
}
|
|
.newproj-tab:focus,
|
|
.newproj-tab:active {
|
|
background: transparent;
|
|
border-color: transparent;
|
|
outline: none;
|
|
color: var(--text-muted);
|
|
}
|
|
.newproj-tab:focus-visible {
|
|
outline: none;
|
|
box-shadow: inset 0 0 0 2px var(--selected);
|
|
border-radius: 8px 8px 0 0;
|
|
color: var(--text);
|
|
}
|
|
.newproj-tab:hover:not(:disabled) {
|
|
background: transparent;
|
|
border-color: transparent;
|
|
outline: none;
|
|
color: var(--text);
|
|
}
|
|
.newproj-tab.active,
|
|
.newproj-tab.active:hover:not(:disabled),
|
|
.newproj-tab.active:focus,
|
|
.newproj-tab.active:focus-visible {
|
|
color: var(--text);
|
|
background: var(--bg-panel);
|
|
border-color: var(--border);
|
|
border-bottom-color: var(--bg-panel);
|
|
z-index: 3;
|
|
}
|
|
.newproj-body {
|
|
margin: 0 24px;
|
|
padding: 16px 18px 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 0 0 12px 12px;
|
|
background: var(--bg-panel);
|
|
box-shadow: var(--shadow-xs);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
/* The parent `.newproj` is `overflow: hidden`; the card needs to be the
|
|
scroll container so taller form variants (image/media tabs, validation
|
|
messages, compact-height windows) stay reachable inside the sidebar.
|
|
`flex: 0 1 auto` keeps the card at its natural content height when the
|
|
form is short (so there's no big white void at the bottom of the
|
|
card), but lets `flex-shrink: 1` collapse it down to the available
|
|
space when content is long — at which point `overflow-y: auto` takes
|
|
over and the body scrolls. */
|
|
flex: 0 1 auto;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
}
|
|
.newproj-title {
|
|
margin: 0;
|
|
font-size: 13px;
|
|
font-weight: 550;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.newproj-title-text { flex: 1 1 auto; min-width: 0; }
|
|
.newproj-title-badge {
|
|
flex: 0 0 auto;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-pill);
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-muted);
|
|
font-size: 10px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
line-height: 1.4;
|
|
}
|
|
.newproj-name { width: 100%; height: 30px; padding: 0 10px; }
|
|
.newproj-section { display: flex; flex-direction: column; gap: 6px; }
|
|
.platform-picker-hint {
|
|
margin: 0;
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
line-height: 1.35;
|
|
}
|
|
/* PlatformPicker reuses the DS picker visual language (`.ds-picker-*`)
|
|
declared further down, so its bespoke `.platform-dropdown-*` rules were
|
|
removed — the dropdown trigger and popover render through the shared
|
|
`.ds-picker-trigger` / `.ds-picker-popover` / `.ds-picker-item` styles
|
|
for a neutral, accent-free look that matches the rest of the panel. */
|
|
.newproj-label {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
font-weight: 500;
|
|
}
|
|
.newproj-media-options {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 14px;
|
|
}
|
|
.newproj-media-field,
|
|
.newproj-media-options .newproj-label {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
.newproj-model-groups {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
.newproj-model-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.newproj-provider-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.11em;
|
|
text-transform: uppercase;
|
|
}
|
|
.newproj-provider-badge {
|
|
border-radius: 999px;
|
|
padding: 2px 8px;
|
|
font-size: 10px;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.newproj-provider-badge.configured {
|
|
border: 1px solid color-mix(in srgb, #4169e1 24%, transparent);
|
|
background: color-mix(in srgb, #4169e1 10%, transparent);
|
|
color: #3155c9;
|
|
}
|
|
.newproj-provider-badge.integrated {
|
|
border: 1px solid color-mix(in srgb, var(--accent) 24%, transparent);
|
|
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
color: color-mix(in srgb, var(--accent) 78%, var(--text-strong));
|
|
}
|
|
.newproj-provider-badge.unsupported {
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-subtle);
|
|
color: var(--text-muted);
|
|
}
|
|
.newproj-model-grid,
|
|
.newproj-option-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.newproj-option-grid.aspect-grid {
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
}
|
|
.newproj-option-grid.compact {
|
|
grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
|
|
}
|
|
.newproj-card {
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
background: var(--bg-panel);
|
|
color: var(--text-strong);
|
|
box-shadow: var(--shadow-xs);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: border-color 140ms ease, background 140ms ease, box-shadow 140ms ease;
|
|
}
|
|
.newproj-card:hover {
|
|
border-color: var(--border-strong);
|
|
background: var(--bg-subtle);
|
|
}
|
|
.newproj-card.active {
|
|
border-color: var(--border-strong);
|
|
background: var(--bg-subtle);
|
|
box-shadow: none;
|
|
}
|
|
.newproj-model-card {
|
|
min-height: 74px;
|
|
padding: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
}
|
|
.newproj-model-name {
|
|
font-family: var(--font-mono);
|
|
font-size: 14px;
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
}
|
|
.newproj-model-hint {
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
line-height: 1.35;
|
|
}
|
|
.newproj-option-card {
|
|
min-height: 62px;
|
|
min-width: 0;
|
|
padding: 10px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 7px;
|
|
text-align: center;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
.newproj-option-card small {
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
}
|
|
.aspect-grid .newproj-option-card {
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
padding: 10px 6px;
|
|
}
|
|
.aspect-copy {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
align-items: center;
|
|
line-height: 1.15;
|
|
min-width: 0;
|
|
max-width: 100%;
|
|
}
|
|
.aspect-copy strong {
|
|
color: var(--text-muted);
|
|
font-size: 12.5px;
|
|
font-weight: 650;
|
|
letter-spacing: -0.005em;
|
|
max-width: 100%;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.aspect-copy small {
|
|
color: var(--text-muted);
|
|
font-size: 11.5px;
|
|
}
|
|
.aspect-glyph {
|
|
flex: none;
|
|
width: 34px;
|
|
height: 24px;
|
|
border-radius: 4px;
|
|
background: var(--bg-subtle);
|
|
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border) 80%, transparent);
|
|
}
|
|
.aspect-1-1 { width: 22px; height: 22px; }
|
|
.aspect-16-9 { width: 32px; height: 18px; }
|
|
.aspect-9-16 { width: 18px; height: 32px; }
|
|
.aspect-4-3 { width: 28px; height: 21px; }
|
|
.aspect-3-4 { width: 21px; height: 28px; }
|
|
@media (max-width: 560px) {
|
|
.newproj-model-grid,
|
|
.newproj-option-grid,
|
|
.newproj-option-grid.aspect-grid {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
}
|
|
.newproj-skills {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
max-height: 220px;
|
|
overflow-y: auto;
|
|
padding-right: 4px;
|
|
}
|
|
.skill-radio {
|
|
display: flex;
|
|
gap: 8px;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
align-items: flex-start;
|
|
background: var(--bg-panel);
|
|
}
|
|
.skill-radio:hover { background: var(--bg-subtle); border-color: var(--border-strong); }
|
|
.skill-radio.active {
|
|
border-color: var(--accent);
|
|
background: var(--accent-tint);
|
|
}
|
|
.skill-radio input { width: auto; margin-top: 2px; }
|
|
.skill-radio-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
.skill-radio-name { font-weight: 500; font-size: 13px; }
|
|
.skill-radio-desc { font-size: 11px; color: var(--text-muted); line-height: 1.4; }
|
|
.newproj-empty {
|
|
font-size: 12px; color: var(--text-muted);
|
|
font-style: italic; padding: 8px 0;
|
|
}
|
|
.video-body,
|
|
.audio-body {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 24px;
|
|
background: var(--bg-subtle);
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
.video-body video {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
border-radius: var(--radius-sm);
|
|
background: #000;
|
|
}
|
|
.audio-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
width: min(100%, 480px);
|
|
padding: 28px 32px;
|
|
color: var(--text-muted);
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
}
|
|
.audio-card-name {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
.audio-card audio {
|
|
width: 100%;
|
|
}
|
|
.newproj-create {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
padding: 8px 11px;
|
|
margin-top: 2px;
|
|
font-size: 13px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.newproj-import {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
width: 100%;
|
|
padding: 6px 10px;
|
|
font-size: 12.5px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.newproj-create svg,
|
|
.newproj-import svg {
|
|
display: block;
|
|
flex: 0 0 auto;
|
|
}
|
|
.newproj-open-folder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-top: 2px;
|
|
}
|
|
.newproj-folder-input {
|
|
width: 100%;
|
|
height: 30px;
|
|
padding: 0 10px;
|
|
font-size: 12px;
|
|
font-family: var(--font-mono, monospace);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-input, var(--bg));
|
|
color: var(--text);
|
|
box-sizing: border-box;
|
|
}
|
|
.newproj-folder-input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
}
|
|
.newproj-folder-input::placeholder {
|
|
color: var(--text-muted);
|
|
opacity: 0.7;
|
|
}
|
|
.newproj-footer {
|
|
padding: 6px 24px 16px;
|
|
font-size: 11px;
|
|
color: var(--text-muted);
|
|
text-align: center;
|
|
flex: 0 0 auto;
|
|
/* Sits a fixed, tight distance below the body card. Visible card→text
|
|
gap = padding-top (6). */
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* -------- Fidelity cards (prototype tab) ---------------------------- */
|
|
.fidelity-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 8px;
|
|
}
|
|
.fidelity-card {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
gap: 4px;
|
|
padding: 6px 6px 8px;
|
|
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;
|
|
}
|
|
.fidelity-card:hover { border-color: var(--border-strong); }
|
|
.fidelity-card.active {
|
|
border-color: var(--selected);
|
|
box-shadow:
|
|
0 0 0 1px var(--selected),
|
|
0 1px 0 rgba(37, 99, 235, 0.04);
|
|
}
|
|
.fidelity-thumb {
|
|
display: block;
|
|
width: 100%;
|
|
aspect-ratio: 16 / 5;
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
background: var(--bg-subtle);
|
|
border: 1px solid var(--border-soft);
|
|
}
|
|
.fidelity-thumb-wireframe { background: #fbfaf6; }
|
|
.fidelity-thumb-high-fidelity { background: var(--bg-panel); }
|
|
.fidelity-card.active .fidelity-thumb { border-color: var(--selected-soft); }
|
|
.fidelity-label {
|
|
text-align: center;
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
}
|
|
|
|
/* -------- Toggle row (deck "speaker notes", template "animations") -- */
|
|
.toggle-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
padding: 12px 14px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: border-color 120ms ease, background 120ms ease;
|
|
}
|
|
.toggle-row:hover { border-color: var(--border-strong); }
|
|
.toggle-row.on { border-color: var(--border-strong); background: var(--bg-subtle); }
|
|
.toggle-row.disabled,
|
|
.toggle-row:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.58;
|
|
background: var(--bg-subtle);
|
|
}
|
|
.toggle-row.disabled:hover,
|
|
.toggle-row:disabled:hover { border-color: var(--border); }
|
|
.toggle-row-text {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.toggle-row-label {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
}
|
|
.toggle-row-hint {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.4;
|
|
}
|
|
.toggle-row-switch {
|
|
flex: none;
|
|
position: relative;
|
|
width: 32px;
|
|
height: 18px;
|
|
border-radius: 999px;
|
|
background: var(--border-strong);
|
|
transition: background 160ms ease;
|
|
}
|
|
.toggle-row-switch::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 2px;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
box-shadow: 0 1px 2px rgba(28, 27, 26, 0.18);
|
|
transition: transform 160ms cubic-bezier(0.2, 0, 0.2, 1);
|
|
}
|
|
.toggle-row.on .toggle-row-switch { background: var(--text); }
|
|
.toggle-row.on .toggle-row-switch::after { transform: translateX(14px); }
|
|
|
|
/* -------- Compact toggle (lightweight inline variant used by
|
|
SurfaceOptions; one-line, no border, hint moves to a native tooltip). -- */
|
|
.compact-toggle-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.compact-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
width: 100%;
|
|
min-height: 36px;
|
|
padding: 4px 10px;
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: background 120ms ease;
|
|
}
|
|
.compact-toggle:hover { background: var(--bg-subtle); }
|
|
.compact-toggle.disabled,
|
|
.compact-toggle:disabled {
|
|
cursor: not-allowed;
|
|
opacity: 0.58;
|
|
background: transparent;
|
|
}
|
|
.compact-toggle.disabled:hover,
|
|
.compact-toggle:disabled:hover { background: transparent; }
|
|
.compact-toggle-label {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 12.5px;
|
|
color: var(--text);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.compact-toggle-switch {
|
|
flex: none;
|
|
position: relative;
|
|
width: 28px;
|
|
height: 16px;
|
|
border-radius: 999px;
|
|
background: var(--border-strong);
|
|
transition: background 160ms ease;
|
|
}
|
|
.compact-toggle-switch::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 2px;
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
box-shadow: 0 1px 2px rgba(28, 27, 26, 0.18);
|
|
transition: transform 160ms cubic-bezier(0.2, 0, 0.2, 1);
|
|
}
|
|
.compact-toggle.on .compact-toggle-switch { background: var(--text); }
|
|
.compact-toggle.on .compact-toggle-switch::after { transform: translateX(12px); }
|
|
|
|
/* -------- Template picker (template tab) ---------------------------- */
|
|
.template-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
.template-option {
|
|
display: flex;
|
|
align-items: center;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
text-align: left;
|
|
transition: border-color 120ms ease, background 120ms ease;
|
|
}
|
|
.template-option:hover { border-color: var(--border-strong); }
|
|
.template-option.active {
|
|
border-color: var(--accent);
|
|
background: var(--accent-tint);
|
|
}
|
|
.template-option-select {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 12px 14px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
}
|
|
.template-option-delete {
|
|
flex: none;
|
|
padding: 4px 10px;
|
|
margin-right: 8px;
|
|
background: none;
|
|
border: none;
|
|
cursor: pointer;
|
|
color: var(--text-muted);
|
|
font-size: 12px;
|
|
border-radius: var(--radius-sm);
|
|
opacity: 0;
|
|
transition: opacity 120ms ease, color 120ms ease;
|
|
}
|
|
.template-option:hover .template-option-delete { opacity: 1; }
|
|
.template-option-delete:hover { color: var(--danger, #e53e3e); }
|
|
.template-radio {
|
|
flex: none;
|
|
margin-top: 2px;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
border: 1.5px solid var(--border-strong);
|
|
background: var(--bg-panel);
|
|
position: relative;
|
|
}
|
|
.template-radio.active {
|
|
border-color: var(--accent);
|
|
background: var(--bg-panel);
|
|
}
|
|
.template-radio.active::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 2px;
|
|
border-radius: 50%;
|
|
background: var(--accent);
|
|
}
|
|
.template-option-text {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.template-option-name {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
}
|
|
.template-option-desc {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.4;
|
|
}
|
|
.template-howto {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
padding: 12px 14px;
|
|
background: var(--bg-subtle);
|
|
border: 1px dashed var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
.template-howto-title {
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
}
|
|
.template-howto-body {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.45;
|
|
}
|
|
|
|
/* -------- New project · connectors section (live-artifact tab) ----- */
|
|
.newproj-connectors { gap: 8px; }
|
|
.newproj-connectors-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
.newproj-connectors-manage {
|
|
padding: 2px 8px;
|
|
font-size: 11px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.02em;
|
|
color: var(--text-muted);
|
|
background: transparent;
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
cursor: pointer;
|
|
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
}
|
|
.newproj-connectors-manage:hover {
|
|
color: var(--text);
|
|
border-color: var(--border-strong);
|
|
background: var(--bg-subtle);
|
|
}
|
|
.newproj-connectors-hint {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.45;
|
|
}
|
|
.newproj-connectors-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.newproj-connector-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 10px 4px 8px;
|
|
font-size: 11.5px;
|
|
color: var(--text);
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
max-width: 100%;
|
|
min-width: 0;
|
|
}
|
|
.newproj-connector-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 999px;
|
|
background: #3bb273;
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, #3bb273 22%, transparent);
|
|
flex: none;
|
|
}
|
|
.newproj-connector-name {
|
|
font-weight: 500;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 140px;
|
|
}
|
|
.newproj-connector-account {
|
|
color: var(--text-muted);
|
|
font-size: 11px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 120px;
|
|
}
|
|
.newproj-connectors-empty {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
width: 100%;
|
|
padding: 12px 14px;
|
|
background: var(--bg-subtle);
|
|
border: 1px dashed var(--border-strong);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;
|
|
}
|
|
.newproj-connectors-empty:hover {
|
|
border-color: var(--accent);
|
|
border-style: solid;
|
|
background: color-mix(in srgb, var(--accent) 4%, var(--bg-subtle));
|
|
}
|
|
.newproj-connectors-empty:active { transform: translateY(1px); }
|
|
.newproj-connectors-empty:focus-visible {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 30%, transparent);
|
|
}
|
|
.newproj-connectors-empty-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 26px;
|
|
height: 26px;
|
|
flex: none;
|
|
border-radius: 8px;
|
|
background: var(--bg-panel);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-muted);
|
|
}
|
|
.newproj-connectors-empty:hover .newproj-connectors-empty-icon {
|
|
color: var(--accent);
|
|
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
|
|
}
|
|
.newproj-connectors-empty-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.newproj-connectors-empty-title {
|
|
font-size: 12.5px;
|
|
font-weight: 500;
|
|
color: var(--text);
|
|
}
|
|
.newproj-connectors-empty-body {
|
|
font-size: 11.5px;
|
|
color: var(--text-muted);
|
|
line-height: 1.45;
|
|
}
|
|
.newproj-connectors-empty-cta {
|
|
margin-top: 4px;
|
|
font-size: 11.5px;
|
|
font-weight: 500;
|
|
color: var(--accent);
|
|
}
|