Merge main into the light theme branch
main now carries the token-based shell and chat from #104 and #107, so this branch takes main's version of the shell, stage, styles and roster row files and keeps what main still lacks: the light palette, the cn() font-size fix, the centered stage bloom, and light-weight shadows for the selected sidebar row and the stage orb. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016or6oqCqP8wbNafvhnbLQT
This commit is contained in:
commit
ce0e7fc8db
262
apps/web/e2e/chat-contrast.spec.ts
Normal file
262
apps/web/e2e/chat-contrast.spec.ts
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import { expect, type Locator, type Page, test } from "@playwright/test";
|
||||||
|
import {
|
||||||
|
captureScreenshot,
|
||||||
|
completeOnboarding,
|
||||||
|
createNamedBot,
|
||||||
|
openNewBot,
|
||||||
|
rpc,
|
||||||
|
signup,
|
||||||
|
waitForChatComposer,
|
||||||
|
} from "./helpers";
|
||||||
|
|
||||||
|
// WCAG AA for body text.
|
||||||
|
const MIN_CONTRAST = 4.5;
|
||||||
|
|
||||||
|
test.use({ viewport: { width: 1280, height: 1000 } });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texts in `locator` below WCAG AA against the surface they are painted on. With `inside`, every
|
||||||
|
* visible text and typed value within it counts, except in disabled controls, which WCAG exempts.
|
||||||
|
*/
|
||||||
|
function unreadableTexts(locator: Locator, inside: boolean) {
|
||||||
|
return locator.evaluate(
|
||||||
|
(root, { inside, min }) => {
|
||||||
|
const context = document
|
||||||
|
.createElement("canvas")
|
||||||
|
.getContext("2d", { willReadFrequently: true });
|
||||||
|
if (!context) throw new Error("No 2D canvas context");
|
||||||
|
// The canvas normalizes every CSS color syntax (oklab, color-mix, ...) to sRGB.
|
||||||
|
const rgba = (color: string) => {
|
||||||
|
context.clearRect(0, 0, 1, 1);
|
||||||
|
context.fillStyle = color;
|
||||||
|
context.fillRect(0, 0, 1, 1);
|
||||||
|
const [r = 0, g = 0, b = 0, a = 0] = context.getImageData(0, 0, 1, 1).data;
|
||||||
|
return [r / 255, g / 255, b / 255, a / 255] as const;
|
||||||
|
};
|
||||||
|
// One pixel of `color`, painted inside `from` and composited through every ancestor's
|
||||||
|
// background color and opacity onto the white canvas.
|
||||||
|
const paint = (color: readonly [number, number, number, number], from: Element) => {
|
||||||
|
let [r, g, b, a] = color;
|
||||||
|
for (let node: Element | null = from; node; node = node.parentElement) {
|
||||||
|
const style = getComputedStyle(node);
|
||||||
|
const [br, bg, bb, ba] = rgba(style.backgroundColor);
|
||||||
|
const alpha = a + ba * (1 - a);
|
||||||
|
if (alpha > 0) {
|
||||||
|
r = (r * a + br * ba * (1 - a)) / alpha;
|
||||||
|
g = (g * a + bg * ba * (1 - a)) / alpha;
|
||||||
|
b = (b * a + bb * ba * (1 - a)) / alpha;
|
||||||
|
}
|
||||||
|
a = alpha * Number(style.opacity);
|
||||||
|
}
|
||||||
|
return [r * a + 1 - a, g * a + 1 - a, b * a + 1 - a] as const;
|
||||||
|
};
|
||||||
|
const luminance = (rgb: readonly [number, number, number]) => {
|
||||||
|
const [r = 0, g = 0, b = 0] = rgb.map((channel) =>
|
||||||
|
channel <= 0.04045 ? channel / 12.92 : ((channel + 0.055) / 1.055) ** 2.4,
|
||||||
|
);
|
||||||
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
||||||
|
};
|
||||||
|
const contrast = (element: Element) => {
|
||||||
|
const text = luminance(paint(rgba(getComputedStyle(element).color), element));
|
||||||
|
const surface = luminance(paint([0, 0, 0, 0], element));
|
||||||
|
return (Math.max(text, surface) + 0.05) / (Math.min(text, surface) + 0.05);
|
||||||
|
};
|
||||||
|
// What the element itself shows: a typed value, or its own text nodes.
|
||||||
|
const ownText = (element: Element) => {
|
||||||
|
if (element instanceof HTMLTextAreaElement) return element.value;
|
||||||
|
if (element instanceof HTMLInputElement) {
|
||||||
|
return /^(text|search|url|email|tel|number|password)$/.test(element.type)
|
||||||
|
? element.value
|
||||||
|
: "";
|
||||||
|
}
|
||||||
|
return Array.from(element.childNodes, (node) =>
|
||||||
|
node.nodeType === Node.TEXT_NODE ? node.textContent : "",
|
||||||
|
)
|
||||||
|
.join("")
|
||||||
|
.trim();
|
||||||
|
};
|
||||||
|
const shown = (element: Element) => {
|
||||||
|
const { width, height } = element.getBoundingClientRect();
|
||||||
|
return width > 1 && height > 1 && element.checkVisibility();
|
||||||
|
};
|
||||||
|
const elements = inside
|
||||||
|
? [root, ...root.querySelectorAll("*")].filter(
|
||||||
|
(element) => ownText(element) && shown(element) && !element.closest(":disabled"),
|
||||||
|
)
|
||||||
|
: [root];
|
||||||
|
return elements.flatMap((element) => {
|
||||||
|
const ratio = contrast(element);
|
||||||
|
const text = ownText(element) || element.textContent?.trim() || "";
|
||||||
|
return ratio < min ? [`${ratio.toFixed(2)}:1 ${text.slice(0, 48)}`] : [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ inside, min: MIN_CONTRAST },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectReadable(locator: Locator, what: string, { inside = false } = {}) {
|
||||||
|
await expect(locator).toBeVisible();
|
||||||
|
await expect
|
||||||
|
.poll(() => unreadableTexts(locator, inside), { message: `${what} is readable` })
|
||||||
|
.toEqual([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function inEachTheme(page: Page, check: (theme: "light" | "dark") => Promise<void>) {
|
||||||
|
for (const theme of ["light", "dark"] as const) {
|
||||||
|
await page.emulateMedia({ colorScheme: theme });
|
||||||
|
await expect(page.locator("html")).toHaveAttribute("data-theme", theme);
|
||||||
|
await check(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function runStatus(page: Page, groupId: string) {
|
||||||
|
return rpc<{ run?: { status: string } | null }>(page, "threads/get", { groupId }).then(
|
||||||
|
(snapshot) => snapshot.run?.status ?? "idle",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function send(page: Page, groupId: string, text: string) {
|
||||||
|
await expect
|
||||||
|
.poll(() => runStatus(page, groupId), { timeout: 60_000 })
|
||||||
|
.not.toMatch(/^(queued|running)$/);
|
||||||
|
const composer = page.getByRole("combobox", { name: "Message" });
|
||||||
|
await composer.fill(text);
|
||||||
|
await composer.press("Enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
test("conversation text stays readable in light and dark themes", async ({ page }, testInfo) => {
|
||||||
|
const stamp = Date.now();
|
||||||
|
await page.emulateMedia({ colorScheme: "light" });
|
||||||
|
await signup(page, `chat-contrast-${stamp}@rakazo.test`, "password12", "Contrast QA");
|
||||||
|
await completeOnboarding(page, undefined, { mainConversation: true });
|
||||||
|
const groupId = new URL(page.url()).pathname.split("/").at(-1)!;
|
||||||
|
await rpc(page, "bots/create", {
|
||||||
|
name: "Researcher",
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
instructions: "",
|
||||||
|
notifyOnFinish: true,
|
||||||
|
});
|
||||||
|
await page.reload();
|
||||||
|
await waitForChatComposer(page);
|
||||||
|
await expect(page.locator("html")).toHaveAttribute("data-theme", "light");
|
||||||
|
const transcript = page.getByTestId("transcript");
|
||||||
|
|
||||||
|
// The scripted bot echoes the request, so its reply renders this markdown.
|
||||||
|
await send(
|
||||||
|
page,
|
||||||
|
groupId,
|
||||||
|
"Summarize the **owl facts** in `notes/owls.md`\n\n## Owl notes\n\n> Owls turn their heads\n\n```\nowls = 3\n```",
|
||||||
|
);
|
||||||
|
const firstMessage = transcript
|
||||||
|
.getByTestId("message-user-bubble")
|
||||||
|
.filter({ hasText: "Summarize the **owl facts**" });
|
||||||
|
const markdown = transcript
|
||||||
|
.locator(".rk-chat-markdown")
|
||||||
|
.filter({ hasText: "done. i handled: Summarize the owl facts" });
|
||||||
|
const reply = markdown.locator("p").filter({ hasText: "done. i handled" });
|
||||||
|
await expect(reply).toBeVisible({ timeout: 60_000 });
|
||||||
|
|
||||||
|
await send(page, groupId, "delegate to the bot named Researcher: collect three owl facts");
|
||||||
|
const task = transcript.getByTestId("delegated-task-card");
|
||||||
|
await expect(task).toHaveAttribute("data-status", "done", { timeout: 60_000 });
|
||||||
|
await expect(
|
||||||
|
transcript.getByText("closing the task: collect three owl facts.").first(),
|
||||||
|
).toBeVisible({ timeout: 60_000 });
|
||||||
|
|
||||||
|
await send(page, groupId, "ask me which city to use");
|
||||||
|
await expect.poll(() => runStatus(page, groupId), { timeout: 60_000 }).toBe("waiting_input");
|
||||||
|
const question = transcript
|
||||||
|
.locator(".rk-chat-markdown p")
|
||||||
|
.filter({ hasText: /^Which city should I use\?$/ });
|
||||||
|
// threads/get can observe waiting_input before the realtime feed paints the ask card.
|
||||||
|
if ((await question.count()) === 0) {
|
||||||
|
await page.reload({ waitUntil: "domcontentloaded" });
|
||||||
|
await waitForChatComposer(page);
|
||||||
|
}
|
||||||
|
await expect(question).toBeVisible({ timeout: 15_000 });
|
||||||
|
const lastMessage = transcript
|
||||||
|
.getByTestId("message-user-bubble")
|
||||||
|
.filter({ hasText: "ask me which city to use" });
|
||||||
|
await task.locator("summary").click();
|
||||||
|
const brief = task.locator(".rk-chat-markdown p");
|
||||||
|
await expect(brief).toHaveText("collect three owl facts");
|
||||||
|
|
||||||
|
await inEachTheme(page, async (theme) => {
|
||||||
|
await firstMessage.scrollIntoViewIfNeeded();
|
||||||
|
await captureScreenshot(page, testInfo, `chat-contrast-${theme}-reply`);
|
||||||
|
await expectReadable(firstMessage, `${theme}: the user's own message`);
|
||||||
|
await expectReadable(reply, `${theme}: a bot reply`);
|
||||||
|
await expectReadable(reply.locator("strong"), `${theme}: bold text in a bot reply`);
|
||||||
|
await expectReadable(reply.locator("code"), `${theme}: inline code in a bot reply`);
|
||||||
|
await expectReadable(markdown.locator("h2"), `${theme}: a heading in a bot reply`);
|
||||||
|
await expectReadable(markdown.locator("blockquote p"), `${theme}: a quote in a bot reply`);
|
||||||
|
await expectReadable(markdown.locator("pre code"), `${theme}: a code block in a bot reply`);
|
||||||
|
|
||||||
|
await brief.scrollIntoViewIfNeeded();
|
||||||
|
await captureScreenshot(page, testInfo, `chat-contrast-${theme}-task`);
|
||||||
|
await expectReadable(brief, `${theme}: the delegated task brief`);
|
||||||
|
|
||||||
|
await question.scrollIntoViewIfNeeded();
|
||||||
|
await captureScreenshot(page, testInfo, `chat-contrast-${theme}-ask`);
|
||||||
|
await expectReadable(question, `${theme}: the ask card question`);
|
||||||
|
await expectReadable(lastMessage, `${theme}: the latest user message`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shell surfaces stay readable in light and dark themes", async ({ page }, testInfo) => {
|
||||||
|
await page.emulateMedia({ colorScheme: "light" });
|
||||||
|
await signup(page, `shell-contrast-${Date.now()}@rakazo.test`, "password12", "Contrast QA");
|
||||||
|
await completeOnboarding(page);
|
||||||
|
await createNamedBot(page, "Researcher");
|
||||||
|
const composer = page.getByRole("combobox", { name: "Message" });
|
||||||
|
await composer.fill("Draft a note about owls");
|
||||||
|
const sidebar = page.getByTestId("bots-sidebar");
|
||||||
|
const sidePanel = page.getByTestId("side-panel");
|
||||||
|
|
||||||
|
await inEachTheme(page, async (theme) => {
|
||||||
|
await captureScreenshot(page, testInfo, `shell-contrast-${theme}`);
|
||||||
|
await expectReadable(composer, `${theme}: the composer draft`);
|
||||||
|
await expectReadable(page.getByTestId("composer-bar"), `${theme}: the composer`, {
|
||||||
|
inside: true,
|
||||||
|
});
|
||||||
|
await expectReadable(
|
||||||
|
sidebar.getByRole("button", { name: "Heute", exact: true }),
|
||||||
|
`${theme}: Heute`,
|
||||||
|
{ inside: true },
|
||||||
|
);
|
||||||
|
await expectReadable(
|
||||||
|
sidebar.locator("[data-main-conversation]"),
|
||||||
|
`${theme}: the main conversation row`,
|
||||||
|
{ inside: true },
|
||||||
|
);
|
||||||
|
await expectReadable(
|
||||||
|
sidebar.getByRole("button", { name: "Researcher, 1:1 conversation" }),
|
||||||
|
`${theme}: the selected bot row`,
|
||||||
|
{ inside: true },
|
||||||
|
);
|
||||||
|
await expectReadable(page.getByTestId("trading-mode-status"), `${theme}: trading status`, {
|
||||||
|
inside: true,
|
||||||
|
});
|
||||||
|
await expectReadable(page.getByTestId("user-menu-trigger"), `${theme}: the user menu`, {
|
||||||
|
inside: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await openNewBot(page);
|
||||||
|
await page.getByTestId("create-bot-form").locator("label:has-text('Name') input").fill("Owl");
|
||||||
|
await inEachTheme(page, async (theme) => {
|
||||||
|
await captureScreenshot(page, testInfo, `shell-contrast-${theme}-new-bot`);
|
||||||
|
await expectReadable(sidePanel, `${theme}: the new bot form`, { inside: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Cancel new bot" }).click();
|
||||||
|
await page.getByTestId("bot-settings-trigger").click();
|
||||||
|
const advanced = page.getByTestId("bot-settings-advanced");
|
||||||
|
await advanced.locator("summary").click();
|
||||||
|
await expect(advanced.getByText("Open work")).toBeVisible();
|
||||||
|
await inEachTheme(page, async (theme) => {
|
||||||
|
await captureScreenshot(page, testInfo, `shell-contrast-${theme}-settings`);
|
||||||
|
await expectReadable(sidePanel, `${theme}: the bot settings form`, { inside: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -175,7 +175,7 @@ export function OoStage({
|
|||||||
{(hasMessages || phase !== "idle") && (
|
{(hasMessages || phase !== "idle") && (
|
||||||
<div className={`flex w-full justify-center ${hasMessages ? "mb-1" : "mb-3"}`}>
|
<div className={`flex w-full justify-center ${hasMessages ? "mb-1" : "mb-3"}`}>
|
||||||
<div
|
<div
|
||||||
className="inline-flex items-center gap-2 rounded-full border border-foreground/10 bg-foreground/[0.05] px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm"
|
className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm"
|
||||||
style={
|
style={
|
||||||
reducedMotion
|
reducedMotion
|
||||||
? undefined
|
? undefined
|
||||||
@ -280,7 +280,7 @@ function StageEnsemble({
|
|||||||
return (
|
return (
|
||||||
<li key={member.botId} className="flex flex-col items-center gap-1" title={member.name}>
|
<li key={member.botId} className="flex flex-col items-center gap-1" title={member.name}>
|
||||||
<div
|
<div
|
||||||
className={`relative flex size-14 items-center justify-center rounded-full border border-border bg-card backdrop-blur-md dark:border-foreground/12 dark:bg-slate-950/40 ${
|
className={`relative flex size-14 items-center justify-center rounded-full border border-border bg-muted/40 backdrop-blur-md ${
|
||||||
isWorking && !reducedMotion ? "animate-pulse" : ""
|
isWorking && !reducedMotion ? "animate-pulse" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -484,7 +484,7 @@ export function OoGroupStage({
|
|||||||
className={`relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-4 md:px-8 ${hasMessages ? "py-2 md:py-3" : "py-5 md:py-6"}`}
|
className={`relative z-10 mx-auto flex w-full max-w-3xl flex-col items-center px-4 md:px-8 ${hasMessages ? "py-2 md:py-3" : "py-5 md:py-6"}`}
|
||||||
>
|
>
|
||||||
<div className="mb-3 w-full min-w-0 text-center">
|
<div className="mb-3 w-full min-w-0 text-center">
|
||||||
<div className="inline-flex items-center gap-2 rounded-full border border-foreground/10 bg-foreground/[0.05] px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm">
|
<div className="inline-flex items-center gap-2 rounded-full border border-border bg-foreground/5 px-2.5 py-0.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground backdrop-blur-sm">
|
||||||
<span
|
<span
|
||||||
className={`size-1.5 rounded-full ${
|
className={`size-1.5 rounded-full ${
|
||||||
running ? "bg-violet-400" : "bg-emerald-400"
|
running ? "bg-violet-400" : "bg-emerald-400"
|
||||||
|
|||||||
@ -9,12 +9,12 @@ export type StagePresencePhase =
|
|||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
const PHASE_BAR_COLOR: Record<StagePresencePhase, string> = {
|
const PHASE_BAR_COLOR: Record<StagePresencePhase, string> = {
|
||||||
idle: "bg-emerald-500/70 dark:bg-emerald-400/60",
|
idle: "bg-emerald-400/60",
|
||||||
working: "bg-violet-500/75 dark:bg-violet-400/70",
|
working: "bg-violet-400/70",
|
||||||
listening: "bg-cyan-500/90 dark:bg-cyan-400/90",
|
listening: "bg-cyan-400/90",
|
||||||
thinking: "bg-violet-500/75 dark:bg-violet-400/70",
|
thinking: "bg-violet-400/70",
|
||||||
speaking: "bg-emerald-500/90 dark:bg-emerald-400/90",
|
speaking: "bg-emerald-400/90",
|
||||||
error: "bg-rose-500/80 dark:bg-rose-400/80",
|
error: "bg-rose-400/80",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function StageHero({
|
export function StageHero({
|
||||||
@ -55,7 +55,7 @@ export function StageHero({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`relative flex ${orbSize} items-center justify-center rounded-full border border-border bg-card shadow-[0_12px_32px_-12px_rgba(24,24,22,0.18)] backdrop-blur-xl dark:border-foreground/14 dark:bg-slate-950/45 dark:shadow-[0_16px_48px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.12)]`}
|
className={`relative flex ${orbSize} items-center justify-center rounded-full border border-border bg-muted/45 shadow-[0_12px_32px_-12px_rgba(24,24,22,0.18)] backdrop-blur-xl dark:shadow-[0_16px_48px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.12)]`}
|
||||||
>
|
>
|
||||||
{reducedMotion ? null : (
|
{reducedMotion ? null : (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -34,11 +34,8 @@ export function TextStageCanvas({
|
|||||||
const personaTint = ACCENT_TINT[personaAccent];
|
const personaTint = ACCENT_TINT[personaAccent];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div aria-hidden className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||||
aria-hidden
|
<div className="absolute inset-0 bg-radial-[ellipse_100%_70%_at_50%_0%] from-card/55 to-transparent to-68%" />
|
||||||
className="pointer-events-none absolute inset-0 overflow-hidden opacity-60 dark:opacity-100"
|
|
||||||
>
|
|
||||||
<div className="absolute inset-0 dark:bg-[radial-gradient(ellipse_100%_70%_at_50%_0%,rgba(12,16,24,0.55)_0%,transparent_68%)]" />
|
|
||||||
<div
|
<div
|
||||||
className={`absolute left-1/2 top-0 h-[min(48vh,320px)] w-[min(72vw,480px)] -translate-x-1/2 rounded-full blur-[72px] ${reducedMotion ? "" : "voice-bloom-emerald"}`}
|
className={`absolute left-1/2 top-0 h-[min(48vh,320px)] w-[min(72vw,480px)] -translate-x-1/2 rounded-full blur-[72px] ${reducedMotion ? "" : "voice-bloom-emerald"}`}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@ -28,15 +28,15 @@ export function VoiceAwarenessStrip({
|
|||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
data-testid="voice-awareness"
|
data-testid="voice-awareness"
|
||||||
className="mx-auto flex max-w-2xl items-start gap-3 rounded-[20px] border border-foreground/10 bg-foreground/[0.05] px-4 py-3 backdrop-blur-md"
|
className="mx-auto flex max-w-2xl items-start gap-3 rounded-[20px] border border-border bg-muted/40 px-4 py-3 backdrop-blur-md"
|
||||||
>
|
>
|
||||||
<BotAvatar color={botColor} identity={botId} size={32} className="shrink-0" />
|
<BotAvatar color={botColor} identity={botId} size={32} className="shrink-0" />
|
||||||
<div className="min-w-0 flex-1 text-start">
|
<div className="min-w-0 flex-1 text-start">
|
||||||
<div className="flex items-center gap-2 text-ui-label font-semibold uppercase tracking-wide text-foreground/75">
|
<div className="flex items-center gap-2 text-ui-label font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
<Icon strokeWidth={1.8} className="size-icon-sm shrink-0 opacity-80" />
|
<Icon strokeWidth={1.8} className="size-icon-sm shrink-0 opacity-80" />
|
||||||
<span>{awarenessKindLabel(event.kind)}</span>
|
<span>{awarenessKindLabel(event.kind)}</span>
|
||||||
</div>
|
</div>
|
||||||
<p dir="auto" className="mt-1 text-ui-body leading-relaxed text-foreground/90">
|
<p dir="auto" className="mt-1 text-ui-body leading-relaxed text-foreground">
|
||||||
{label}
|
{label}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -106,7 +106,7 @@ export function ScratchpadSection({ botId }: { botId: string }) {
|
|||||||
<Trans>Open work</Trans>
|
<Trans>Open work</Trans>
|
||||||
</div>
|
</div>
|
||||||
{items.length === 0 ? (
|
{items.length === 0 ? (
|
||||||
<div className="py-1 text-ui-body text-muted-foreground/80">
|
<div className="py-1 text-ui-body text-muted-foreground">
|
||||||
<Trans>None yet</Trans>
|
<Trans>None yet</Trans>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -290,7 +290,6 @@ import {
|
|||||||
rosterRowClass,
|
rosterRowClass,
|
||||||
rosterTimeClass,
|
rosterTimeClass,
|
||||||
rosterUnreadDotClass,
|
rosterUnreadDotClass,
|
||||||
selectedSidebarRowClass,
|
|
||||||
} from "./shell/roster-row-style";
|
} from "./shell/roster-row-style";
|
||||||
import { WindowChrome } from "./WindowChrome";
|
import { WindowChrome } from "./WindowChrome";
|
||||||
|
|
||||||
@ -3355,7 +3354,7 @@ export function ShellPage() {
|
|||||||
<div
|
<div
|
||||||
data-testid="shell-root"
|
data-testid="shell-root"
|
||||||
data-ready={shellReady}
|
data-ready={shellReady}
|
||||||
className="relative flex h-full min-w-0 overflow-hidden bg-sidebar dark:bg-black dark:bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] dark:from-zinc-900 dark:via-black dark:to-black text-foreground/90 p-0 md:p-3 gap-0 md:gap-3"
|
className="relative flex h-full min-w-0 overflow-hidden bg-background text-foreground/90 p-0 md:p-3 gap-0 md:gap-3"
|
||||||
onTouchStartCapture={(event) => {
|
onTouchStartCapture={(event) => {
|
||||||
if (
|
if (
|
||||||
mobileSidebarOpen ||
|
mobileSidebarOpen ||
|
||||||
@ -3429,7 +3428,7 @@ export function ShellPage() {
|
|||||||
width: mobileSidebarOpen ? undefined : botsSidebarCollapsed ? 0 : leftSidebarWidth,
|
width: mobileSidebarOpen ? undefined : botsSidebarCollapsed ? 0 : leftSidebarWidth,
|
||||||
maxWidth: mobileSidebarOpen ? 316 : undefined,
|
maxWidth: mobileSidebarOpen ? 316 : undefined,
|
||||||
}}
|
}}
|
||||||
className={`absolute inset-y-0 start-0 z-40 flex w-[calc(100%-48px)] shrink-0 flex-col md:rounded-2xl md:border md:border-transparent md:shadow-none dark:md:border-foreground/10 dark:md:shadow-2xl bg-sidebar md:static md:z-auto md:translate-x-0 ${
|
className={`absolute inset-y-0 start-0 z-40 flex w-[calc(100%-48px)] shrink-0 flex-col md:rounded-2xl md:border md:border-sidebar-border md:shadow-2xl bg-sidebar md:static md:z-auto md:translate-x-0 ${
|
||||||
mobileSidebarOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full"
|
mobileSidebarOpen ? "translate-x-0" : "-translate-x-full rtl:translate-x-full"
|
||||||
} ${
|
} ${
|
||||||
botsSidebarCollapsed
|
botsSidebarCollapsed
|
||||||
@ -3566,13 +3565,9 @@ export function ShellPage() {
|
|||||||
setActiveNavPillar("heute");
|
setActiveNavPillar("heute");
|
||||||
setMobileSidebarOpen(false);
|
setMobileSidebarOpen(false);
|
||||||
}}
|
}}
|
||||||
className={`flex w-full items-center gap-3 rounded-2xl px-3 py-3 text-start transition-all duration-150 cursor-pointer min-h-[52px] ${
|
className={rosterRowClass(activeNavPillar === "heute")}
|
||||||
activeNavPillar === "heute"
|
|
||||||
? selectedSidebarRowClass
|
|
||||||
: "hover:bg-foreground/[0.04]"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
<div className="relative grid size-control-lg shrink-0 place-items-center rounded-full bg-emerald-500/10 text-emerald-700 dark:text-emerald-400">
|
<div className="relative grid size-control-lg shrink-0 place-items-center rounded-full bg-muted text-muted-foreground">
|
||||||
<Calendar strokeWidth={2.2} className="size-icon-md" />
|
<Calendar strokeWidth={2.2} className="size-icon-md" />
|
||||||
{heuteActionCount > 0 ? (
|
{heuteActionCount > 0 ? (
|
||||||
<span
|
<span
|
||||||
@ -3827,11 +3822,9 @@ export function ShellPage() {
|
|||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={`absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-sidebar ${
|
className={`absolute -bottom-0.5 -right-0.5 size-2.5 rounded-full border-2 border-sidebar ${
|
||||||
rosterStatusDot(item.chat.status) === "needs-you"
|
rosterStatusDot(item.chat.status) === "idle"
|
||||||
? "bg-warning animate-pulse"
|
? "bg-success"
|
||||||
: rosterStatusDot(item.chat.status) === "working"
|
: "bg-warning animate-pulse"
|
||||||
? "bg-amber-500 animate-pulse dark:bg-amber-400"
|
|
||||||
: "bg-emerald-500 dark:bg-emerald-400"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -3852,7 +3845,7 @@ export function ShellPage() {
|
|||||||
{needsYouChipLabel(item.chat.status)}
|
{needsYouChipLabel(item.chat.status)}
|
||||||
</span>
|
</span>
|
||||||
) : item.chat.title ? (
|
) : item.chat.title ? (
|
||||||
<span className="min-w-0 truncate rounded-full border border-emerald-500/25 bg-emerald-500/10 px-2 py-0.5 text-ui-label font-medium text-emerald-700 dark:text-emerald-400">
|
<span className="min-w-0 truncate rounded-full border border-border bg-muted px-2 py-0.5 text-ui-label font-medium text-muted-foreground">
|
||||||
{item.chat.title}
|
{item.chat.title}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@ -3906,7 +3899,7 @@ export function ShellPage() {
|
|||||||
})}
|
})}
|
||||||
<div className="sidebar-projekte">
|
<div className="sidebar-projekte">
|
||||||
{/* PROJEKTE SECTION */}
|
{/* PROJEKTE SECTION */}
|
||||||
<div className="flex items-center justify-between pt-4 pb-1 mt-3 border-t border-foreground/5 px-2.5">
|
<div className="flex items-center justify-between pt-4 pb-1 mt-3 border-t border-sidebar-border px-2.5">
|
||||||
<div>
|
<div>
|
||||||
<div className="rk-shell-section-label">{t`Projekte`}</div>
|
<div className="rk-shell-section-label">{t`Projekte`}</div>
|
||||||
<p className="mt-0.5 px-0.5 text-ui-caption text-muted-foreground/70">
|
<p className="mt-0.5 px-0.5 text-ui-caption text-muted-foreground/70">
|
||||||
@ -4093,15 +4086,11 @@ export function ShellPage() {
|
|||||||
position: { x: event.clientX, y: event.clientY },
|
position: { x: event.clientX, y: event.clientY },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className={`flex w-full items-center gap-3 rounded-2xl px-3 py-3 mb-[2px] text-start transition-all duration-150 cursor-pointer min-h-[52px] ${
|
className={rosterRowClass(
|
||||||
item.kind === "bot" && !inGroup && active?.id === item.chat.id
|
item.kind === "bot"
|
||||||
? selectedSidebarRowClass
|
? !inGroup && active?.id === item.chat.id
|
||||||
: item.kind === "group" &&
|
: inGroup && activeGroup?.id === item.chat.id,
|
||||||
inGroup &&
|
)}
|
||||||
activeGroup?.id === item.chat.id
|
|
||||||
? selectedSidebarRowClass
|
|
||||||
: "hover:bg-foreground/[0.05] border border-transparent"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{item.kind === "bot" ? (
|
{item.kind === "bot" ? (
|
||||||
<BotAvatar
|
<BotAvatar
|
||||||
@ -4118,34 +4107,30 @@ export function ShellPage() {
|
|||||||
<div className="flex min-w-0 items-center gap-2 overflow-hidden">
|
<div className="flex min-w-0 items-center gap-2 overflow-hidden">
|
||||||
<span
|
<span
|
||||||
dir="auto"
|
dir="auto"
|
||||||
className={`shrink-0 max-w-[170px] truncate text-ui-body tracking-tight ${
|
className={`shrink-0 max-w-[170px] truncate text-ui-body tracking-tight ${rosterNameClass(
|
||||||
item.chat.unread
|
item.chat.unread,
|
||||||
? "font-bold text-foreground"
|
)}`}
|
||||||
: "font-semibold text-foreground/95"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{item.chat.name}
|
{item.chat.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex shrink-0 items-center gap-1.5">
|
<div className="flex shrink-0 items-center gap-1.5">
|
||||||
<span className="text-ui-body-sm text-muted-foreground tabular-nums font-medium">
|
<span className={rosterTimeClass}>
|
||||||
{formatRosterTime(item.chat.updatedAt)}
|
{formatRosterTime(item.chat.updatedAt)}
|
||||||
</span>
|
</span>
|
||||||
{item.chat.unread ? (
|
{item.chat.unread ? (
|
||||||
<span
|
<span
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="inline-block h-2 w-2 rounded-full bg-emerald-400"
|
className={rosterUnreadDotClass}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
dir="auto"
|
dir="auto"
|
||||||
className={`mt-0.5 truncate text-ui-body leading-snug ${
|
className={`mt-0.5 truncate text-ui-body leading-snug ${rosterPreviewClass(
|
||||||
item.chat.unread
|
item.chat.unread,
|
||||||
? "font-medium text-foreground"
|
)}`}
|
||||||
: "text-foreground/75"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{cleanRosterPreview(item.chat.preview)}
|
{cleanRosterPreview(item.chat.preview)}
|
||||||
</div>
|
</div>
|
||||||
@ -4357,7 +4342,7 @@ export function ShellPage() {
|
|||||||
<main
|
<main
|
||||||
aria-hidden={mobileSidebarOpen || undefined}
|
aria-hidden={mobileSidebarOpen || undefined}
|
||||||
inert={mobileSidebarOpen}
|
inert={mobileSidebarOpen}
|
||||||
className="flex min-w-0 flex-1 flex-col bg-background md:rounded-2xl md:border md:border-border md:shadow-sm dark:md:border-foreground/10 dark:md:shadow-2xl overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]"
|
className="flex min-w-0 flex-1 flex-col bg-background md:rounded-2xl md:border md:border-border md:shadow-2xl overflow-hidden transition-all duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]"
|
||||||
>
|
>
|
||||||
{activeNavPillar === "heute" ? (
|
{activeNavPillar === "heute" ? (
|
||||||
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
<div className="flex h-full flex-1 flex-col overflow-hidden">
|
||||||
@ -4719,7 +4704,7 @@ export function ShellPage() {
|
|||||||
? rightSidebarWidth
|
? rightSidebarWidth
|
||||||
: 0,
|
: 0,
|
||||||
}}
|
}}
|
||||||
className={`absolute inset-y-0 end-0 z-20 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:rounded-2xl md:border md:border-border md:shadow-sm dark:md:border-foreground/10 dark:md:shadow-2xl md:relative ${
|
className={`absolute inset-y-0 end-0 z-20 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:rounded-2xl md:border md:border-border md:shadow-2xl md:relative ${
|
||||||
panel &&
|
panel &&
|
||||||
(
|
(
|
||||||
active ||
|
active ||
|
||||||
@ -4823,8 +4808,8 @@ export function ShellPage() {
|
|||||||
/>
|
/>
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
devServerRunning ? (
|
devServerRunning ? (
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-emerald-500/10 px-2 py-0.5 text-ui-label font-medium text-emerald-700 dark:text-emerald-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-ui-label font-medium text-foreground">
|
||||||
<span className="size-1.5 rounded-full bg-emerald-400" />
|
<span className="size-1.5 rounded-full bg-success" />
|
||||||
<Trans>Dev</Trans>
|
<Trans>Dev</Trans>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@ -5003,7 +4988,7 @@ export function ShellPage() {
|
|||||||
aria-label={t`Close`}
|
aria-label={t`Close`}
|
||||||
onClick={() => setShowCreateBotModal(false)}
|
onClick={() => setShowCreateBotModal(false)}
|
||||||
/>
|
/>
|
||||||
<div className="relative z-10 max-h-[90vh] w-[480px] overflow-y-auto rounded-2xl border border-border bg-popover p-6 shadow-2xl">
|
<div className="relative z-10 max-h-[90vh] w-[480px] overflow-y-auto rounded-2xl border border-border bg-popover p-6 text-popover-foreground shadow-2xl">
|
||||||
<div className="mb-4 flex items-center justify-between">
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<h2 className="text-lg font-semibold text-foreground">
|
<h2 className="text-lg font-semibold text-foreground">
|
||||||
<Trans>Create new Bot</Trans>
|
<Trans>Create new Bot</Trans>
|
||||||
@ -6109,10 +6094,9 @@ const Transcript = memo(function Transcript({
|
|||||||
<EvooMascot size={76} pose="greeting" interactive trackPointer="hover" />
|
<EvooMascot size={76} pose="greeting" interactive trackPointer="hover" />
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-3xl font-bold tracking-tight text-foreground mb-2">
|
<h2 className="text-3xl font-bold tracking-tight text-foreground mb-2">
|
||||||
{t`Guten Tag.`} <span className="text-emerald-700 dark:text-emerald-400">evOo</span>{" "}
|
{t`Guten Tag.`} evOo {t`ist bereit.`}
|
||||||
{t`ist bereit.`}
|
|
||||||
</h2>
|
</h2>
|
||||||
<p className="max-w-md text-ui-body leading-relaxed text-foreground/75 mb-8 font-normal">
|
<p className="max-w-md text-ui-body leading-relaxed text-foreground/85 mb-8 font-normal">
|
||||||
{activeName
|
{activeName
|
||||||
? t`Frag ${activeName} nach Marktchancen, autonomen Tasks oder Architektur-Reviews.`
|
? t`Frag ${activeName} nach Marktchancen, autonomen Tasks oder Architektur-Reviews.`
|
||||||
: t`Wähle einen autonomen Agenten aus deinem Team oder starte direkt einen Prompt.`}
|
: t`Wähle einen autonomen Agenten aus deinem Team oder starte direkt einen Prompt.`}
|
||||||
@ -6123,10 +6107,10 @@ const Transcript = memo(function Transcript({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onPromptSelect?.(t`⚡ Führe einen autonomen Portfolio- und Risiko-Check durch`);
|
onPromptSelect?.(t`⚡ Führe einen autonomen Portfolio- und Risiko-Check durch`);
|
||||||
}}
|
}}
|
||||||
className="apple-glass-card group flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] hover:border-emerald-500/40 cursor-pointer"
|
className="apple-glass-card flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="text-xl mb-2">⚡</span>
|
<span className="text-xl mb-2">⚡</span>
|
||||||
<span className="text-[14.5px] font-semibold text-foreground group-hover:text-emerald-700 dark:group-hover:text-emerald-400 transition-colors">
|
<span className="text-[14.5px] font-semibold text-foreground">
|
||||||
{t`Portfolio-Check`}
|
{t`Portfolio-Check`}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
||||||
@ -6138,10 +6122,10 @@ const Transcript = memo(function Transcript({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onPromptSelect?.(t`📈 Scanne den Markt nach aktuellen Alpha-Signalen`);
|
onPromptSelect?.(t`📈 Scanne den Markt nach aktuellen Alpha-Signalen`);
|
||||||
}}
|
}}
|
||||||
className="apple-glass-card group flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] hover:border-sky-500/40 cursor-pointer"
|
className="apple-glass-card flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="text-xl mb-2">📈</span>
|
<span className="text-xl mb-2">📈</span>
|
||||||
<span className="text-[14.5px] font-semibold text-foreground group-hover:text-sky-700 dark:group-hover:text-sky-400 transition-colors">
|
<span className="text-[14.5px] font-semibold text-foreground">
|
||||||
{t`Alpha-Signale`}
|
{t`Alpha-Signale`}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
||||||
@ -6153,10 +6137,10 @@ const Transcript = memo(function Transcript({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
onPromptSelect?.(t`💎 Führe ein Steve Jobs UX & Architektur-Audit durch`);
|
onPromptSelect?.(t`💎 Führe ein Steve Jobs UX & Architektur-Audit durch`);
|
||||||
}}
|
}}
|
||||||
className="apple-glass-card group flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] hover:border-purple-500/40 cursor-pointer"
|
className="apple-glass-card flex flex-col items-start p-4 rounded-2xl text-start transition-all hover:scale-[1.02] cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="text-xl mb-2">💎</span>
|
<span className="text-xl mb-2">💎</span>
|
||||||
<span className="text-[14.5px] font-semibold text-foreground group-hover:text-purple-700 dark:group-hover:text-purple-400 transition-colors">
|
<span className="text-[14.5px] font-semibold text-foreground">
|
||||||
{t`Steve Jobs Audit`}
|
{t`Steve Jobs Audit`}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
<span className="text-ui-body-sm text-muted-foreground mt-1 leading-snug">
|
||||||
@ -6735,8 +6719,8 @@ const Composer = memo(function Composer({
|
|||||||
{/* Quick Action Prompt Chips */}
|
{/* Quick Action Prompt Chips */}
|
||||||
{showComposerPlaceholder ? (
|
{showComposerPlaceholder ? (
|
||||||
<div className="mb-3 flex items-center gap-2 overflow-x-auto no-scrollbar py-1">
|
<div className="mb-3 flex items-center gap-2 overflow-x-auto no-scrollbar py-1">
|
||||||
<span className="flex items-center gap-1.5 text-ui-label font-semibold uppercase tracking-wider text-emerald-700 dark:text-emerald-400 shrink-0 px-2.5 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 me-1 shadow-xs">
|
<span className="flex items-center gap-1.5 text-ui-label font-semibold uppercase tracking-wider text-muted-foreground shrink-0 px-2.5 py-1 rounded-full bg-muted/40 border border-border me-1 shadow-xs">
|
||||||
<Sparkles className="text-amber-700 dark:text-amber-400 size-icon-sm" />
|
<Sparkles className="text-amber-400 size-icon-sm" />
|
||||||
Quick
|
Quick
|
||||||
</span>
|
</span>
|
||||||
{quickPromptsForBot(activeName).map((promptText) => (
|
{quickPromptsForBot(activeName).map((promptText) => (
|
||||||
@ -6747,7 +6731,7 @@ const Composer = memo(function Composer({
|
|||||||
updateDraft(promptText);
|
updateDraft(promptText);
|
||||||
focusComposer();
|
focusComposer();
|
||||||
}}
|
}}
|
||||||
className="shrink-0 rounded-full border border-foreground/[0.1] bg-foreground/[0.05] px-3.5 py-1.5 text-ui-body-sm font-medium text-foreground/85 hover:border-foreground/20 dark:hover:border-emerald-500/40 hover:bg-foreground/[0.1] hover:text-foreground transition-all shadow-sm active:scale-95 cursor-pointer"
|
className="shrink-0 rounded-full border border-border bg-muted/40 px-3.5 py-1.5 text-ui-body-sm font-medium text-foreground/85 hover:bg-accent hover:text-foreground transition-all shadow-sm active:scale-95 cursor-pointer"
|
||||||
>
|
>
|
||||||
{promptText}
|
{promptText}
|
||||||
</button>
|
</button>
|
||||||
@ -6930,7 +6914,7 @@ const Composer = memo(function Composer({
|
|||||||
) : null}
|
) : null}
|
||||||
<div
|
<div
|
||||||
data-testid="composer-bar"
|
data-testid="composer-bar"
|
||||||
className="apple-island relative flex items-center gap-3.5 rounded-[30px] py-3 pe-3.5 ps-4 transition-all duration-200 focus-within:border-foreground/25 dark:focus-within:border-emerald-500/40 dark:focus-within:shadow-[0_20px_50px_-10px_rgba(0,0,0,0.85),inset_0_1px_0_rgba(255,255,255,0.22),0_0_0_1px_rgba(16,185,129,0.35)] dark:shadow-[0_16px_40px_-8px_rgba(0,0,0,0.7),inset_0_1px_0_rgba(255,255,255,0.14)]"
|
className="apple-island relative flex items-center gap-3.5 rounded-[30px] py-3 pe-3.5 ps-4 transition-all duration-200"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
@ -6946,7 +6930,7 @@ const Composer = memo(function Composer({
|
|||||||
aria-label={t`Attach file`}
|
aria-label={t`Attach file`}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => fileInputRef.current?.click()}
|
onClick={() => fileInputRef.current?.click()}
|
||||||
className="tactile-press size-10 shrink-0 rounded-full border border-foreground/10 bg-foreground/[0.06] text-foreground/80 transition-all hover:bg-foreground/[0.12] hover:text-foreground flex items-center justify-center cursor-pointer"
|
className="tactile-press size-10 shrink-0 rounded-full border border-border bg-foreground/5 text-foreground/80 transition-all hover:bg-foreground/10 hover:text-foreground flex items-center justify-center cursor-pointer"
|
||||||
>
|
>
|
||||||
<Plus strokeWidth={2.2} className="size-icon-lg" />
|
<Plus strokeWidth={2.2} className="size-icon-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -6954,12 +6938,9 @@ const Composer = memo(function Composer({
|
|||||||
{selectedSkill ? (
|
{selectedSkill ? (
|
||||||
<span
|
<span
|
||||||
data-testid="skill-chip"
|
data-testid="skill-chip"
|
||||||
className="inline-flex max-w-full items-center gap-1.5 rounded-full border border-foreground/10 bg-foreground/[0.08] px-3 py-1 text-ui-body font-medium text-foreground backdrop-blur-md"
|
className="inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-foreground/[0.08] px-3 py-1 text-ui-body font-medium text-foreground backdrop-blur-md"
|
||||||
>
|
>
|
||||||
<Box
|
<Box strokeWidth={1.8} className="shrink-0 text-muted-foreground size-icon-md" />
|
||||||
strokeWidth={1.8}
|
|
||||||
className="shrink-0 text-blue-700 dark:text-blue-400 size-icon-md"
|
|
||||||
/>
|
|
||||||
<span dir="auto" className="truncate">
|
<span dir="auto" className="truncate">
|
||||||
{selectedSkill.name}
|
{selectedSkill.name}
|
||||||
</span>
|
</span>
|
||||||
@ -6978,7 +6959,7 @@ const Composer = memo(function Composer({
|
|||||||
key={mentionChipKey(mention)}
|
key={mentionChipKey(mention)}
|
||||||
data-testid="mention-chip"
|
data-testid="mention-chip"
|
||||||
data-mention-kind={mention.kind}
|
data-mention-kind={mention.kind}
|
||||||
className="inline-flex max-w-full items-center gap-1.5 rounded-full border border-foreground/10 bg-foreground/[0.08] px-3 py-1 text-ui-body font-medium text-foreground backdrop-blur-md"
|
className="inline-flex max-w-full items-center gap-1.5 rounded-full border border-border bg-foreground/[0.08] px-3 py-1 text-ui-body font-medium text-foreground backdrop-blur-md"
|
||||||
>
|
>
|
||||||
<MentionChipIcon mention={mention} />
|
<MentionChipIcon mention={mention} />
|
||||||
<span dir="auto" className="truncate">
|
<span dir="auto" className="truncate">
|
||||||
@ -7058,7 +7039,7 @@ const Composer = memo(function Composer({
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
rows={1}
|
rows={1}
|
||||||
className="max-h-36 min-h-[28px] min-w-[8rem] flex-1 resize-none overflow-y-auto bg-transparent py-1 text-[16.5px] leading-[1.55] text-foreground outline-none placeholder:text-muted-foreground/75 disabled:opacity-40 font-normal selection:bg-emerald-500/30"
|
className="max-h-36 min-h-[28px] min-w-[8rem] flex-1 resize-none overflow-y-auto bg-transparent py-1 text-[16.5px] leading-[1.55] text-foreground outline-none placeholder:text-muted-foreground/75 disabled:opacity-40 font-normal"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{onVoice ? (
|
{onVoice ? (
|
||||||
@ -7091,7 +7072,7 @@ const Composer = memo(function Composer({
|
|||||||
aria-label={t`Send`}
|
aria-label={t`Send`}
|
||||||
disabled={!canSend || disabled}
|
disabled={!canSend || disabled}
|
||||||
onClick={send}
|
onClick={send}
|
||||||
className="tactile-press size-10 rounded-full bg-primary text-primary-foreground hover:bg-primary/85 dark:bg-emerald-400 dark:text-slate-950 dark:hover:bg-emerald-300 dark:shadow-[0_0_16px_rgba(16,185,129,0.35)] transition-transform active:scale-90 flex items-center justify-center cursor-pointer"
|
className="tactile-press size-10 rounded-full transition-transform active:scale-90 flex items-center justify-center cursor-pointer"
|
||||||
>
|
>
|
||||||
<ArrowUp strokeWidth={2.6} className="size-icon-lg" />
|
<ArrowUp strokeWidth={2.6} className="size-icon-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -7101,7 +7082,7 @@ const Composer = memo(function Composer({
|
|||||||
aria-label={t`Stop`}
|
aria-label={t`Stop`}
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
onClick={() => void onStop()}
|
onClick={() => void onStop()}
|
||||||
className="tactile-press size-10 rounded-full border border-foreground/15 bg-foreground/[0.08] text-foreground/90 shadow-sm transition-colors hover:bg-foreground/[0.15] hover:text-foreground flex items-center justify-center cursor-pointer"
|
className="tactile-press size-10 rounded-full border border-border bg-foreground/[0.08] text-foreground/90 shadow-sm transition-colors hover:bg-foreground/15 hover:text-foreground flex items-center justify-center cursor-pointer"
|
||||||
>
|
>
|
||||||
<Square strokeWidth={0} fill="currentColor" className="size-icon-sm" />
|
<Square strokeWidth={0} fill="currentColor" className="size-icon-sm" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -7112,7 +7093,7 @@ const Composer = memo(function Composer({
|
|||||||
aria-label={t`Send`}
|
aria-label={t`Send`}
|
||||||
disabled={!canSend || disabled}
|
disabled={!canSend || disabled}
|
||||||
onClick={send}
|
onClick={send}
|
||||||
className="tactile-press size-10 shrink-0 rounded-full bg-primary text-primary-foreground hover:bg-primary/85 dark:bg-emerald-400 dark:text-slate-950 dark:hover:bg-emerald-300 dark:shadow-[0_0_16px_rgba(16,185,129,0.35)] transition-all active:scale-90 disabled:bg-foreground/[0.08] disabled:text-foreground/20 disabled:shadow-none dark:disabled:bg-foreground/[0.08] dark:disabled:text-foreground/20 dark:disabled:shadow-none flex items-center justify-center cursor-pointer"
|
className="tactile-press size-10 shrink-0 rounded-full transition-all active:scale-90 disabled:bg-foreground/[0.08] disabled:text-foreground/20 flex items-center justify-center cursor-pointer"
|
||||||
>
|
>
|
||||||
<ArrowUp strokeWidth={2.6} className="size-icon-lg" />
|
<ArrowUp strokeWidth={2.6} className="size-icon-lg" />
|
||||||
</Button>
|
</Button>
|
||||||
@ -7509,7 +7490,7 @@ const MessageView = memo(function MessageView({
|
|||||||
{effectiveSpeakerName && message.role === "bot" && !encounterMode ? (
|
{effectiveSpeakerName && message.role === "bot" && !encounterMode ? (
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<div className="relative flex items-center justify-center size-7 rounded-xl bg-foreground/[0.08] border border-foreground/[0.12] shadow-xs">
|
<div className="relative flex items-center justify-center size-7 rounded-xl bg-foreground/[0.08] border border-border shadow-xs">
|
||||||
<EvooMascot size={22} interactive={false} pose={isLive ? "working" : "idle"} />
|
<EvooMascot size={22} interactive={false} pose={isLive ? "working" : "idle"} />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -7519,7 +7500,7 @@ const MessageView = memo(function MessageView({
|
|||||||
>
|
>
|
||||||
{effectiveSpeakerName}
|
{effectiveSpeakerName}
|
||||||
</span>
|
</span>
|
||||||
<span className="rounded-full bg-emerald-500/15 border border-emerald-500/25 px-2 py-0.5 text-ui-label font-medium text-emerald-700 dark:text-emerald-400">
|
<span className="rounded-full border border-border bg-muted px-2 py-0.5 text-ui-label font-medium text-muted-foreground">
|
||||||
Oo
|
Oo
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -7880,8 +7861,8 @@ const MessageView = memo(function MessageView({
|
|||||||
data-testid="message-user-bubble"
|
data-testid="message-user-bubble"
|
||||||
className={
|
className={
|
||||||
encounterMode
|
encounterMode
|
||||||
? "max-w-full whitespace-pre-wrap wrap-anywhere rounded-2xl border border-foreground/10 bg-foreground/[0.06] px-3.5 py-2 text-ui-body-sm leading-relaxed text-foreground/95"
|
? "max-w-full whitespace-pre-wrap wrap-anywhere rounded-2xl bg-chat-user px-3.5 py-2 text-ui-body-sm leading-relaxed text-chat-user-foreground"
|
||||||
: "chat-bubble-user max-w-full whitespace-pre-wrap wrap-anywhere rounded-[24px] px-6 py-4 text-[17px] leading-[1.62] text-chat-user-foreground dark:text-[#FAFAFA] dark:shadow-md"
|
: "chat-bubble-user max-w-full whitespace-pre-wrap wrap-anywhere rounded-[24px] px-6 py-4 text-[17px] leading-[1.62] text-chat-user-foreground shadow-md"
|
||||||
}
|
}
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
@ -7903,7 +7884,7 @@ const MessageView = memo(function MessageView({
|
|||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
<ChatMarkdown>{block.text}</ChatMarkdown>
|
<ChatMarkdown>{block.text}</ChatMarkdown>
|
||||||
<div className="mt-3 pt-2.5 flex items-center justify-between border-t border-foreground/[0.08] text-ui-label text-muted-foreground/75">
|
<div className="mt-3 pt-2.5 flex items-center justify-between border-t border-border text-ui-label text-muted-foreground/75">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@ -441,7 +441,7 @@ export function BotSettings({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<label htmlFor={`${ids}-name`} className="mt-4 block text-ui-body text-muted-foreground/80">
|
<label htmlFor={`${ids}-name`} className={fieldLabelClass}>
|
||||||
<Trans>Name</Trans>
|
<Trans>Name</Trans>
|
||||||
<Input
|
<Input
|
||||||
id={`${ids}-name`}
|
id={`${ids}-name`}
|
||||||
@ -484,10 +484,7 @@ export function BotSettings({
|
|||||||
>
|
>
|
||||||
<Trans>Notifications</Trans>
|
<Trans>Notifications</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div id={`${ids}-notify-finish-desc`} className="text-ui-body-sm text-muted-foreground">
|
||||||
id={`${ids}-notify-finish-desc`}
|
|
||||||
className="text-ui-body-sm text-muted-foreground/70"
|
|
||||||
>
|
|
||||||
<Trans>Get notified when this Bot finishes or needs input</Trans>
|
<Trans>Get notified when this Bot finishes or needs input</Trans>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -428,7 +428,7 @@ export function ChartBlockView({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={() => setExpanded(true)}
|
onClick={() => setExpanded(true)}
|
||||||
className="absolute end-3 top-3 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 rounded-full border-white/15 bg-white/10 text-white backdrop-blur-md"
|
className="absolute end-3 top-3 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 rounded-full"
|
||||||
>
|
>
|
||||||
<Trans>Expand</Trans>
|
<Trans>Expand</Trans>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
/** Selected sidebar row: a raised paper row in light, a lit glass row in dark. */
|
|
||||||
export const selectedSidebarRowClass =
|
|
||||||
"border border-sidebar-border bg-sidebar-accent shadow-xs dark:border-emerald-500/30 dark:bg-foreground/[0.09] dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.14),0_4px_16px_rgba(0,0,0,0.4)]";
|
|
||||||
|
|
||||||
/** One look for sidebar conversation rows: the main conversation entry and the bot roster. */
|
/** One look for sidebar conversation rows: the main conversation entry and the bot roster. */
|
||||||
export function rosterRowClass(selected: boolean): string {
|
export function rosterRowClass(selected: boolean): string {
|
||||||
return `flex w-full items-center gap-3 rounded-2xl px-3 py-3 mb-[2px] text-start transition-all duration-150 cursor-pointer min-h-[52px] ${
|
return `flex w-full items-center gap-3 rounded-2xl border px-3 py-3 mb-[2px] text-start transition-all duration-150 cursor-pointer min-h-[52px] ${
|
||||||
selected ? selectedSidebarRowClass : "hover:bg-foreground/[0.05] border border-transparent"
|
selected
|
||||||
|
? "border-sidebar-border bg-sidebar-accent shadow-xs dark:shadow-[inset_0_1px_0_rgba(255,255,255,0.14),0_4px_16px_rgba(0,0,0,0.4)]"
|
||||||
|
: "border-transparent hover:bg-sidebar-accent/50"
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function rosterNameClass(unread: boolean): string {
|
export function rosterNameClass(unread: boolean): string {
|
||||||
return unread ? "font-bold text-foreground" : "font-semibold text-foreground/95";
|
return unread ? "font-bold text-sidebar-foreground" : "font-semibold text-sidebar-foreground";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const rosterTimeClass = "text-ui-body-sm text-muted-foreground tabular-nums font-medium";
|
// Muted text measures about 4.4:1 on the light sidebar; a dimmed foreground stays above 4.5:1.
|
||||||
|
export const rosterTimeClass =
|
||||||
|
"text-ui-body-sm text-sidebar-foreground/65 tabular-nums font-medium";
|
||||||
|
|
||||||
export const rosterUnreadDotClass =
|
export const rosterUnreadDotClass = "inline-block h-2 w-2 rounded-full bg-sidebar-foreground";
|
||||||
"inline-block h-2 w-2 rounded-full bg-emerald-500 dark:bg-emerald-400";
|
|
||||||
|
|
||||||
export function rosterPreviewClass(unread: boolean): string {
|
export function rosterPreviewClass(unread: boolean): string {
|
||||||
return unread ? "font-medium text-foreground" : "text-foreground/75";
|
return unread ? "font-medium text-sidebar-foreground" : "text-sidebar-foreground/85";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,13 +11,7 @@ body,
|
|||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #07090e;
|
background: var(--background);
|
||||||
background-image: radial-gradient(
|
|
||||||
circle at 50% 0%,
|
|
||||||
rgba(16, 185, 129, 0.07) 0%,
|
|
||||||
rgba(14, 165, 233, 0.04) 40%,
|
|
||||||
transparent 80%
|
|
||||||
);
|
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
font-family:
|
font-family:
|
||||||
"Inter Variable", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Geist Variable",
|
"Inter Variable", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Geist Variable",
|
||||||
@ -41,8 +35,8 @@ body,
|
|||||||
.apple-island {
|
.apple-island {
|
||||||
backdrop-filter: blur(36px) saturate(210%);
|
backdrop-filter: blur(36px) saturate(210%);
|
||||||
-webkit-backdrop-filter: blur(36px) saturate(210%);
|
-webkit-backdrop-filter: blur(36px) saturate(210%);
|
||||||
background: rgba(12, 16, 24, 0.88);
|
background: color-mix(in srgb, var(--card) 88%, transparent);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.13);
|
border: 1px solid color-mix(in srgb, var(--foreground) 13%, transparent);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 18px 44px -8px rgba(0, 0, 0, 0.75),
|
0 18px 44px -8px rgba(0, 0, 0, 0.75),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.16);
|
inset 0 1px 0 rgba(255, 255, 255, 0.16);
|
||||||
@ -51,24 +45,24 @@ body,
|
|||||||
.apple-glass-card {
|
.apple-glass-card {
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
-webkit-backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
background: rgba(16, 21, 30, 0.68);
|
background: color-mix(in srgb, var(--card) 68%, transparent);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.09);
|
border: 1px solid var(--border);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
inset 0 1px 0 rgba(255, 255, 255, 0.1),
|
||||||
0 6px 24px rgba(0, 0, 0, 0.35);
|
0 6px 24px rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-bubble-user {
|
.chat-bubble-user {
|
||||||
background: linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.98) 100%);
|
background: var(--chat-user);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
border: 1px solid var(--border);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 8px 24px -4px rgba(0, 0, 0, 0.5),
|
0 8px 24px -4px rgba(0, 0, 0, 0.5),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-bubble-bot {
|
.chat-bubble-bot {
|
||||||
background: rgba(15, 20, 29, 0.72);
|
background: color-mix(in srgb, var(--card) 72%, transparent);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid var(--border);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 10px 32px -4px rgba(0, 0, 0, 0.35),
|
0 10px 32px -4px rgba(0, 0, 0, 0.35),
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||||
@ -76,45 +70,6 @@ body,
|
|||||||
-webkit-backdrop-filter: blur(20px);
|
-webkit-backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Light theme: paper surfaces with hairline borders and soft, short shadows
|
|
||||||
instead of dark glass. */
|
|
||||||
html[data-theme="light"],
|
|
||||||
html[data-theme="light"] body,
|
|
||||||
html[data-theme="light"] #root {
|
|
||||||
background: var(--sidebar);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] .apple-glass,
|
|
||||||
[data-theme="light"] .apple-glass-card {
|
|
||||||
background: color-mix(in srgb, var(--card) 88%, transparent);
|
|
||||||
border-color: var(--border);
|
|
||||||
box-shadow:
|
|
||||||
0 1px 2px rgba(24, 24, 22, 0.04),
|
|
||||||
0 8px 24px -12px rgba(24, 24, 22, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] .apple-island {
|
|
||||||
background: color-mix(in srgb, var(--card) 92%, transparent);
|
|
||||||
border-color: var(--input);
|
|
||||||
box-shadow:
|
|
||||||
0 1px 2px rgba(24, 24, 22, 0.05),
|
|
||||||
0 12px 32px -14px rgba(24, 24, 22, 0.16);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] .chat-bubble-user {
|
|
||||||
background: var(--chat-user);
|
|
||||||
border-color: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="light"] .chat-bubble-bot {
|
|
||||||
background: var(--card);
|
|
||||||
border-color: var(--border);
|
|
||||||
box-shadow: 0 1px 2px rgba(24, 24, 22, 0.04);
|
|
||||||
backdrop-filter: none;
|
|
||||||
-webkit-backdrop-filter: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Shell layout utilities — token-backed, use instead of one-off pixel classes */
|
/* Shell layout utilities — token-backed, use instead of one-off pixel classes */
|
||||||
@layer components {
|
@layer components {
|
||||||
.rk-shell-section-label {
|
.rk-shell-section-label {
|
||||||
@ -134,7 +89,8 @@ html[data-theme="light"] #root {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Gemini-Grade Accessible Typography (Apple-like with Inter Variable) */
|
/* Gemini-Grade Accessible Typography (Apple-like with Inter Variable).
|
||||||
|
Heading, code and quote colors come from the shared @rakazo/chat-ui markdown styles. */
|
||||||
.rk-chat-markdown {
|
.rk-chat-markdown {
|
||||||
font-size: var(--font-size-reading);
|
font-size: var(--font-size-reading);
|
||||||
line-height: var(--line-height-reading);
|
line-height: var(--line-height-reading);
|
||||||
@ -149,8 +105,6 @@ html[data-theme="light"] #root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rk-chat-markdown pre {
|
.rk-chat-markdown pre {
|
||||||
background: rgba(8, 11, 16, 0.96);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.11);
|
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.09),
|
inset 0 1px 0 rgba(255, 255, 255, 0.09),
|
||||||
@ -161,21 +115,12 @@ html[data-theme="light"] #root {
|
|||||||
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] .rk-chat-markdown pre {
|
|
||||||
background: var(--muted);
|
|
||||||
border-color: var(--border);
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-roster-row[data-selected="true"],
|
.sidebar-roster-row[data-selected="true"],
|
||||||
.sidebar-roster-row:hover {
|
.sidebar-roster-row:hover {
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rk-chat-markdown code:not(pre code) {
|
.rk-chat-markdown code:not(pre code) {
|
||||||
background: color-mix(in srgb, var(--foreground) 7%, transparent);
|
|
||||||
border: 1px solid color-mix(in srgb, var(--foreground) 11%, transparent);
|
|
||||||
color: var(--foreground);
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 0.15em 0.4em;
|
padding: 0.15em 0.4em;
|
||||||
@ -188,7 +133,6 @@ html[data-theme="light"] #root {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
color: var(--foreground);
|
|
||||||
margin: 1.6em 0 0.6em;
|
margin: 1.6em 0 0.6em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,7 +141,6 @@ html[data-theme="light"] #root {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
color: var(--foreground);
|
|
||||||
margin: 1.4em 0 0.5em;
|
margin: 1.4em 0 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +149,6 @@ html[data-theme="light"] #root {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
letter-spacing: -0.015em;
|
letter-spacing: -0.015em;
|
||||||
color: var(--foreground);
|
|
||||||
margin: 1.2em 0 0.4em;
|
margin: 1.2em 0 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,29 +164,18 @@ html[data-theme="light"] #root {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.rk-chat-markdown strong {
|
.rk-chat-markdown strong {
|
||||||
color: var(--foreground);
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rk-chat-markdown blockquote {
|
.rk-chat-markdown blockquote {
|
||||||
border-left: 3px solid #10b981;
|
|
||||||
padding-left: 1.2em;
|
padding-left: 1.2em;
|
||||||
margin: 1.3em 0;
|
margin: 1.3em 0;
|
||||||
color: #cbd5e1;
|
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
background: rgba(16, 185, 129, 0.05);
|
|
||||||
border-radius: 0 8px 8px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
padding-top: 0.4em;
|
padding-top: 0.4em;
|
||||||
padding-bottom: 0.4em;
|
padding-bottom: 0.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] .rk-chat-markdown blockquote {
|
|
||||||
border-left-color: var(--input);
|
|
||||||
color: var(--muted-foreground);
|
|
||||||
background: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--spring-snappy: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
--spring-snappy: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
--spring-smooth: cubic-bezier(0.16, 1, 0.3, 1);
|
--spring-smooth: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
@ -262,10 +193,6 @@ html[data-theme="light"] #root {
|
|||||||
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.18);
|
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.18);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="light"] .tactile-press:hover {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tactile-press:active {
|
.tactile-press:active {
|
||||||
transform: scale(0.965);
|
transform: scale(0.965);
|
||||||
transition-duration: 0.06s;
|
transition-duration: 0.06s;
|
||||||
|
|||||||
@ -157,7 +157,6 @@
|
|||||||
.rk-chat-markdown blockquote {
|
.rk-chat-markdown blockquote {
|
||||||
border-left: 3px solid var(--border);
|
border-left: 3px solid var(--border);
|
||||||
padding-left: 0.85em;
|
padding-left: 0.85em;
|
||||||
color: var(--muted-foreground);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rk-chat-markdown table {
|
.rk-chat-markdown table {
|
||||||
|
|||||||
@ -49,7 +49,7 @@ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|||||||
<TabsPrimitive.Tab
|
<TabsPrimitive.Tab
|
||||||
data-slot="tabs-trigger"
|
data-slot="tabs-trigger"
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-ui-body-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-icon-md",
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-ui-body-sm font-medium whitespace-nowrap text-muted-foreground transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-icon-md",
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||||
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
||||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user