mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-27 11:22:03 +00:00
When idle with a top-level delegate_task still in flight, render a static, shimmering system-note at the transcript tail instead of a spinner (which reads as "stuck"). Reuses the shared steer / slash-status chrome (centered, 0.6875rem, muted, Codicon) so it sits in the thread like every other meta line, and mirrors the primary child's latest stream line, falling back to generic copy. i18n across en/ja/zh/zh-hant; markdown prose/heading rhythm tuned so a re-entered turn breathes.
1755 lines
56 KiB
CSS
1755 lines
56 KiB
CSS
@import 'tailwindcss';
|
|
@plugin '@tailwindcss/typography';
|
|
@import 'tw-shimmer';
|
|
@import 'katex/dist/katex.min.css';
|
|
@import '@vscode/codicons/dist/codicon.css';
|
|
@custom-variant dark (&:is(.dark *));
|
|
|
|
/* Sidebar sections: tall viewports give each its own scroller; compact ones
|
|
(this variant) flatten everything into one shared scroll. See ChatSidebar. */
|
|
@custom-variant compact (@media (max-height: 768px));
|
|
|
|
@font-face {
|
|
font-family: 'Collapse';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url('../../../node_modules/@nous-research/ui/dist/fonts/Collapse-Bold.woff2') format('woff2');
|
|
}
|
|
|
|
/* JetBrains Mono — bundled terminal font (Apache-2.0) so bold/italic share the
|
|
regular face's metrics instead of squeezing against a system fallback. */
|
|
@font-face {
|
|
font-family: 'JetBrains Mono';
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('./fonts/JetBrainsMono-Regular.woff2') format('woff2');
|
|
}
|
|
@font-face {
|
|
font-family: 'JetBrains Mono';
|
|
font-style: normal;
|
|
font-weight: 700;
|
|
font-display: swap;
|
|
src: url('./fonts/JetBrainsMono-Bold.woff2') format('woff2');
|
|
}
|
|
@font-face {
|
|
font-family: 'JetBrains Mono';
|
|
font-style: italic;
|
|
font-weight: 400;
|
|
font-display: swap;
|
|
src: url('./fonts/JetBrainsMono-Italic.woff2') format('woff2');
|
|
}
|
|
|
|
@theme inline {
|
|
--color-background: var(--dt-background);
|
|
--color-foreground: var(--dt-foreground);
|
|
--color-card: var(--dt-card);
|
|
--color-card-foreground: var(--dt-card-foreground);
|
|
--color-muted: var(--dt-muted);
|
|
--color-muted-foreground: var(--dt-muted-foreground);
|
|
--color-popover: var(--dt-popover);
|
|
--color-popover-foreground: var(--dt-popover-foreground);
|
|
--color-primary: var(--dt-primary);
|
|
--color-primary-foreground: var(--dt-primary-foreground);
|
|
--color-secondary: var(--dt-secondary);
|
|
--color-secondary-foreground: var(--dt-secondary-foreground);
|
|
--color-accent: var(--dt-accent);
|
|
--color-accent-foreground: var(--dt-accent-foreground);
|
|
--color-border: var(--dt-border);
|
|
--color-input: var(--dt-input);
|
|
--color-ring: var(--dt-ring);
|
|
--color-destructive: var(--dt-destructive);
|
|
--color-destructive-foreground: var(--dt-destructive-foreground);
|
|
|
|
--color-midground: var(--dt-midground);
|
|
--color-midground-foreground: var(--dt-midground-foreground);
|
|
|
|
--font-sans: var(--dt-font-sans);
|
|
--font-mono: var(--dt-font-mono);
|
|
|
|
--spacing-mul: var(--dt-spacing-mul, 1);
|
|
|
|
--radius-xs: calc(var(--radius-scalar) * 0.125rem);
|
|
--radius-sm: calc(var(--radius-scalar) * 0.5rem);
|
|
--radius-md: calc(var(--radius-scalar) * 0.625rem);
|
|
--radius-lg: calc(var(--radius-scalar) * 0.75rem);
|
|
--radius-xl: calc(var(--radius-scalar) * 1rem);
|
|
--radius-2xl: calc(var(--radius-scalar) * 1.5rem);
|
|
--radius-3xl: calc(var(--radius-scalar) * 2rem);
|
|
--radius-4xl: calc(var(--radius-scalar) * 2.5rem);
|
|
|
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
--color-sidebar-border: var(--sidebar-border);
|
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
--color-sidebar-accent: var(--sidebar-accent);
|
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
--color-sidebar-primary: var(--sidebar-primary);
|
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
--color-sidebar: var(--sidebar);
|
|
|
|
--shadow-xs: 0 0.0625rem 0.125rem color-mix(in srgb, #000 5%, transparent);
|
|
--shadow-sm:
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 6%, transparent),
|
|
0 0.125rem 0.5rem color-mix(in srgb, #000 4%, transparent);
|
|
--shadow-md:
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 8%, transparent),
|
|
0 0.25rem 1rem color-mix(in srgb, #000 8%, transparent),
|
|
0 1rem 2rem -1.5rem color-mix(in srgb, #000 18%, transparent);
|
|
/* Soft floating shadow for borderless modals/overlays. Single top light
|
|
source: every layer is centered (x=0) and cast downward, with negative
|
|
spread that grows with the blur so each layer is pulled horizontally inward
|
|
— the shadow pools below the panel instead of bleeding out every side.
|
|
Layered (contact → ambient) for a smooth, natural falloff. */
|
|
--shadow-nous:
|
|
0 0.125rem 0.25rem -0.125rem color-mix(in srgb, #000 7%, transparent),
|
|
0 0.5rem 0.75rem -0.375rem color-mix(in srgb, #000 6%, transparent),
|
|
0 1.25rem 1.75rem -0.875rem color-mix(in srgb, #000 6%, transparent),
|
|
0 2.25rem 3rem -1.75rem color-mix(in srgb, #000 0%, transparent);
|
|
/* Hairline border paired with --shadow-nous on borderless overlays.
|
|
currentColor resolves per-element, so it adapts to text color/theme. */
|
|
--stroke-nous: color-mix(in srgb, currentColor 3%, transparent);
|
|
--shadow-lg:
|
|
inset 0 0.0625rem 0 color-mix(in srgb, #fff 28%, transparent),
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-foreground) 8%, transparent),
|
|
0 0.75rem 2rem color-mix(in srgb, #000 12%, transparent);
|
|
--shadow-composer: 0 0.0625rem 0.125rem color-mix(in srgb, #000 5%, transparent);
|
|
}
|
|
|
|
@layer base {
|
|
:root {
|
|
color-scheme: light;
|
|
|
|
--theme-foreground: #17171a;
|
|
--theme-primary: #0053fd;
|
|
--theme-secondary: color-mix(in srgb, #0053fd 7%, #ffffff);
|
|
--theme-accent-soft: color-mix(in srgb, #0053fd 10%, #ffffff);
|
|
--theme-midground: #0053fd;
|
|
--theme-warm: #cf806d;
|
|
--theme-background-seed: #f8faff;
|
|
--theme-sidebar-seed: #f3f7ff;
|
|
--theme-card-seed: #ffffff;
|
|
--theme-elevated-seed: #ffffff;
|
|
--theme-bubble-seed: color-mix(in srgb, #0053fd 6%, #ffffff);
|
|
--theme-neutral-chrome: #f3f3f3;
|
|
--theme-neutral-sidebar: #f3f3f3;
|
|
--theme-neutral-card: #fcfcfc;
|
|
--theme-mix-chrome: 92%;
|
|
--theme-mix-sidebar: 100%;
|
|
--theme-mix-card: 22%;
|
|
--theme-mix-elevated: 28%;
|
|
--theme-mix-bubble: 0%;
|
|
--theme-fill-primary-accent-mix: 16%;
|
|
--theme-fill-secondary-accent-mix: 11%;
|
|
--theme-fill-tertiary-accent-mix: 8%;
|
|
--theme-fill-quaternary-accent-mix: 5%;
|
|
--theme-fill-quinary-accent-mix: 3%;
|
|
--theme-stroke-primary-accent-mix: 24%;
|
|
--theme-stroke-secondary-accent-mix: 16%;
|
|
--theme-stroke-tertiary-accent-mix: 10%;
|
|
--theme-stroke-quaternary-accent-mix: 6%;
|
|
--theme-row-hover-accent-mix: 4%;
|
|
--theme-row-active-accent-mix: 8%;
|
|
--theme-control-hover-accent-mix: 6%;
|
|
--theme-control-active-accent-mix: 8%;
|
|
|
|
--ui-base: var(--theme-foreground);
|
|
--ui-accent: var(--theme-midground);
|
|
--ui-accent-secondary: var(--theme-primary);
|
|
--ui-warm: var(--theme-warm);
|
|
--ui-red: #cf2d56;
|
|
--ui-orange: #db704b;
|
|
--ui-yellow: #c08532;
|
|
--ui-green: #1f8a65;
|
|
--ui-cyan: #4c7f8c;
|
|
--ui-blue: #0053fd;
|
|
--ui-purple: #9e94d5;
|
|
--ui-bg-chrome: color-mix(
|
|
in srgb,
|
|
var(--theme-background-seed) var(--theme-mix-chrome),
|
|
var(--theme-neutral-chrome)
|
|
);
|
|
--ui-bg-sidebar: color-mix(
|
|
in srgb,
|
|
var(--theme-sidebar-seed) var(--theme-mix-sidebar),
|
|
var(--theme-neutral-sidebar)
|
|
);
|
|
--ui-bg-editor: color-mix(in srgb, var(--theme-card-seed) var(--theme-mix-card), var(--theme-neutral-card));
|
|
--ui-bg-elevated: color-mix(
|
|
in srgb,
|
|
var(--theme-elevated-seed) var(--theme-mix-elevated),
|
|
var(--theme-neutral-card)
|
|
);
|
|
--ui-bg-card: color-mix(in srgb, var(--ui-accent) 4%, color-mix(in srgb, var(--ui-base) 4%, transparent));
|
|
--ui-bg-input: #fcfcfc;
|
|
--ui-bg-primary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-fill-primary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 10%, transparent)
|
|
);
|
|
--ui-bg-secondary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-fill-secondary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 7%, transparent)
|
|
);
|
|
--ui-bg-tertiary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-fill-tertiary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 5%, transparent)
|
|
);
|
|
--ui-bg-quaternary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-fill-quaternary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 4%, transparent)
|
|
);
|
|
--ui-bg-quinary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-fill-quinary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 3%, transparent)
|
|
);
|
|
--ui-row-hover-background: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-row-hover-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 3%, transparent)
|
|
);
|
|
--ui-row-active-background: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-row-active-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 5%, transparent)
|
|
);
|
|
--ui-control-hover-background: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-control-hover-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 4%, transparent)
|
|
);
|
|
--ui-control-active-background: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-control-active-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 5%, transparent)
|
|
);
|
|
--ui-text-primary: color-mix(in srgb, var(--ui-base) 94%, transparent);
|
|
--ui-text-secondary: color-mix(in srgb, var(--ui-base) 74%, transparent);
|
|
--ui-text-tertiary: color-mix(in srgb, var(--ui-base) 54%, transparent);
|
|
--ui-text-quaternary: color-mix(in srgb, var(--ui-base) 36%, transparent);
|
|
--ui-stroke-primary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-stroke-primary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 10%, transparent)
|
|
);
|
|
--ui-stroke-secondary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-stroke-secondary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 7%, transparent)
|
|
);
|
|
--ui-stroke-tertiary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-stroke-tertiary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 5%, transparent)
|
|
);
|
|
--ui-stroke-quaternary: color-mix(
|
|
in srgb,
|
|
var(--ui-accent) var(--theme-stroke-quaternary-accent-mix),
|
|
color-mix(in srgb, var(--ui-base) 3%, transparent)
|
|
);
|
|
--ui-sash-hover-border: color-mix(in srgb, var(--ui-accent) 18%, var(--ui-stroke-tertiary));
|
|
--ui-sash-hover-background: color-mix(in srgb, var(--ui-accent) 6%, transparent);
|
|
--ui-surface-background: var(--ui-bg-editor);
|
|
--ui-sidebar-surface-background: var(--ui-bg-sidebar);
|
|
--ui-chat-surface-background: var(--ui-bg-chrome);
|
|
--ui-editor-surface-background: var(--ui-bg-chrome);
|
|
--ui-chat-bubble-background: color-mix(
|
|
in srgb,
|
|
var(--theme-bubble-seed) var(--theme-mix-bubble),
|
|
var(--theme-neutral-card)
|
|
);
|
|
--ui-chat-bubble-opaque-background: var(--ui-bg-editor);
|
|
--ui-inline-code-background: color-mix(in srgb, #141414 5%, transparent);
|
|
--ui-inline-code-foreground: color-mix(in srgb, #141414 88%, transparent);
|
|
--ui-selection-background: color-mix(in srgb, #ffd24a 55%, transparent);
|
|
|
|
--dt-background: var(--ui-bg-chrome);
|
|
--dt-foreground: var(--ui-text-primary);
|
|
--dt-card: var(--ui-bg-editor);
|
|
--dt-card-foreground: var(--ui-text-primary);
|
|
--dt-muted: var(--ui-bg-tertiary);
|
|
--dt-muted-foreground: var(--ui-text-tertiary);
|
|
--dt-popover: color-mix(in srgb, var(--ui-bg-elevated) 96%, transparent);
|
|
--dt-popover-foreground: var(--ui-text-primary);
|
|
--dt-primary: var(--theme-primary);
|
|
--dt-primary-foreground: #fcfcfc;
|
|
--dt-secondary: var(--theme-secondary);
|
|
--dt-secondary-foreground: var(--ui-text-secondary);
|
|
--dt-accent: var(--theme-accent-soft);
|
|
--dt-accent-foreground: var(--ui-text-primary);
|
|
--dt-border: var(--ui-stroke-secondary);
|
|
--dt-input: var(--ui-stroke-primary);
|
|
--dt-ring: var(--ui-stroke-primary);
|
|
--dt-midground: var(--theme-midground);
|
|
--dt-composer-ring: var(--ui-base);
|
|
--dt-destructive: #cf2d56;
|
|
--dt-destructive-foreground: #ffffff;
|
|
--dt-sidebar-bg: var(--ui-bg-sidebar);
|
|
--dt-sidebar-border: var(--ui-stroke-secondary);
|
|
--dt-user-bubble: var(--ui-chat-bubble-background);
|
|
--dt-user-bubble-border: var(--ui-stroke-tertiary);
|
|
|
|
--dt-font-sans:
|
|
'Segoe WPC', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif,
|
|
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji;
|
|
/* Key caps always use the native UI face — never theme typography overrides. */
|
|
--dt-font-kbd: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
|
|
/* JetBrains Mono first — the face we bundle (@font-face above) and the
|
|
terminal's primary — so code/diff match the terminal on every platform
|
|
instead of drifting to a system Cascadia Code where it's installed. */
|
|
--dt-font-mono:
|
|
'JetBrains Mono', 'Cascadia Code', 'SF Mono', ui-monospace, Menlo, Consolas, monospace, 'Apple Color Emoji',
|
|
'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji', emoji;
|
|
--dt-base-size: 1rem;
|
|
--dt-line-height: 1.5;
|
|
--dt-letter-spacing: 0;
|
|
--dt-spacing-mul: 1;
|
|
|
|
--radius: 0.75rem;
|
|
--radius-scalar: 0.6;
|
|
|
|
/* Space under last message vs overlay composer — driven by the measured composer height (see composer/index.tsx)
|
|
plus the out-of-flow status stack's measured height (see status-stack/index.tsx) when one is showing. */
|
|
--status-stack-measured-height: 0px;
|
|
--thread-last-message-clearance: calc(var(--composer-measured-height) + var(--status-stack-measured-height) + 2rem);
|
|
|
|
--composer-shell-pad-block-end: 0.625rem;
|
|
--message-text-indent: 0.75rem;
|
|
--conversation-text-font-size: 0.8125rem;
|
|
--conversation-tool-font-size: 0.6875rem;
|
|
--conversation-caption-font-size: 0.75rem;
|
|
--conversation-line-height: 1.125rem;
|
|
--conversation-caption-line-height: 1rem;
|
|
--conversation-turn-gap: 0.375rem;
|
|
/* Gap between top-level turn blocks (prose ↔ tools ↔ thinking) — enough air
|
|
that scaffolding reads as separate from the reply, not crammed into it. */
|
|
--turn-block-gap: 0.75rem;
|
|
/* Tight gap between tool rows inside a single action group, so a back-to-back
|
|
run still reads as one cohesive sequence. */
|
|
--tool-row-gap: 0.375rem;
|
|
/* Paragraph spacing — vertical gap between prose paragraphs, both inside a
|
|
markdown block and between consecutive prose parts. Single knob; tweak
|
|
freely. */
|
|
--paragraph-gap: 0.7rem;
|
|
--sticky-human-top: 0.23rem;
|
|
--file-tree-row-height: 1.375rem;
|
|
|
|
--composer-width: 48.75rem;
|
|
--composer-control-size: 1.5rem;
|
|
--composer-control-primary-size: 1.625rem;
|
|
--composer-control-gap: 0.25rem;
|
|
--composer-row-gap: 0.25rem;
|
|
--composer-ring-strength: 1;
|
|
--composer-surface-pad-x: 0.5rem;
|
|
--composer-surface-pad-y: 0.3125rem;
|
|
--composer-input-min-height: 1.625rem;
|
|
--composer-input-max-height: 9.375rem;
|
|
--composer-input-inline-min-width: 8rem;
|
|
--composer-fallback-height: 2.75rem;
|
|
--composer-measured-height: calc(0.5rem + var(--composer-shell-pad-block-end) + var(--composer-fallback-height));
|
|
--composer-surface-measured-height: var(--composer-fallback-height);
|
|
--thread-viewport-height: max(
|
|
0rem,
|
|
calc(100% - var(--composer-measured-height) + var(--composer-surface-measured-height))
|
|
);
|
|
--vsq: min(0.5vh, 0.5vw);
|
|
--image-preview-max-width: 34rem;
|
|
--image-preview-height: clamp(16.25rem, calc(var(--vsq) * 100), 26.25rem);
|
|
|
|
--sidebar-width: 14.8125rem;
|
|
--chat-min-width: 28rem;
|
|
--titlebar-control-size: 1.25rem;
|
|
--titlebar-control-height: 1.375rem;
|
|
--sidebar-content-inline-padding: 1rem;
|
|
|
|
--sidebar: var(--dt-sidebar-bg);
|
|
--sidebar-foreground: var(--dt-foreground);
|
|
--sidebar-primary: var(--dt-primary);
|
|
--sidebar-primary-foreground: var(--dt-primary-foreground);
|
|
--sidebar-accent: var(--ui-control-active-background);
|
|
--sidebar-accent-foreground: var(--dt-accent-foreground);
|
|
--sidebar-border: var(--dt-sidebar-border);
|
|
--sidebar-ring: var(--dt-ring);
|
|
--sidebar-edge-border: color-mix(in srgb, var(--ui-base) 7.5%, transparent);
|
|
--chrome-action-hover: var(--ui-control-hover-background);
|
|
|
|
--midground: var(--dt-midground);
|
|
--background: var(--dt-background);
|
|
--foreground: var(--dt-foreground);
|
|
|
|
--warm-glow: color-mix(in srgb, var(--ui-warm) 32%, color-mix(in srgb, var(--ui-accent) 6%, transparent));
|
|
/* `--noise-opacity-mul` is set per-mode by `applyTheme()`. */
|
|
--noise-opacity-mul: 1;
|
|
--backdrop-invert-mul: 1;
|
|
}
|
|
|
|
:root.dark {
|
|
/* Per-mode mix knobs — overridden inline by `applyTheme()` per skin. */
|
|
--theme-mix-chrome: 74%;
|
|
--theme-mix-card: 38%;
|
|
--theme-mix-elevated: 46%;
|
|
--theme-mix-bubble: 46%;
|
|
--theme-neutral-chrome: #0d0d0e;
|
|
--theme-neutral-sidebar: #0a0a0b;
|
|
--theme-neutral-card: #161618;
|
|
|
|
/* Dark-only accent palette overrides. */
|
|
--ui-red: #e75e78;
|
|
--ui-green: #55a583;
|
|
--ui-cyan: #6f9ba6;
|
|
|
|
--sidebar-edge-border: color-mix(in srgb, var(--ui-base) 12%, transparent);
|
|
--composer-ring-strength: 1.3;
|
|
--backdrop-invert-mul: 0;
|
|
|
|
--ui-inline-code-background: color-mix(in srgb, #ffffff 7%, transparent);
|
|
--ui-inline-code-foreground: color-mix(in srgb, #ffffff 88%, transparent);
|
|
--ui-selection-background: color-mix(in srgb, #ffd24a 38%, transparent);
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
border-color: var(--dt-border);
|
|
}
|
|
|
|
html,
|
|
body,
|
|
#root {
|
|
height: 100%;
|
|
/* App shell, not a document: the window itself never scrolls on either axis
|
|
(panes own their own scroll). Belt to the auto-scroll axis-lock in the
|
|
sidebar reorder DnD — nothing can drag the whole shell sideways. */
|
|
overflow: hidden;
|
|
}
|
|
|
|
html {
|
|
font-size: var(--dt-base-size, 0.875rem);
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
background: var(--ui-chat-surface-background);
|
|
color: var(--dt-foreground);
|
|
font-family: var(--dt-font-sans);
|
|
font-size: 0.8125rem;
|
|
line-height: var(--dt-line-height, 1.55);
|
|
letter-spacing: var(--dt-letter-spacing, 0);
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
button,
|
|
textarea {
|
|
font: inherit;
|
|
}
|
|
|
|
:where(
|
|
a,
|
|
.underline,
|
|
[class~='hover:underline'],
|
|
[class~='focus:underline'],
|
|
[class~='focus-visible:underline'],
|
|
[class~='group-hover:underline'],
|
|
[class~='peer-hover:underline']
|
|
) {
|
|
text-decoration-color: color-mix(in srgb, currentColor 20%, transparent);
|
|
text-underline-offset: 0.25rem;
|
|
}
|
|
|
|
*::selection {
|
|
background: var(--ui-selection-background);
|
|
color: inherit;
|
|
}
|
|
}
|
|
|
|
.dither {
|
|
background: repeating-conic-gradient(currentColor 0% 25%, transparent 0% 50%) 0 0 / 0.125rem 0.125rem;
|
|
}
|
|
|
|
/* Hover-reveal suppression — the shared, declarative escape hatch.
|
|
A collapsed pane slides in when the pointer dwells on its thin edge trigger.
|
|
Controls that sit over that edge gutter would drag the panel in by accident.
|
|
Mark any such region and, while it's hovered, the matching edge trigger(s) go
|
|
pointer-transparent. Auto-resets on mouse-out, no JS.
|
|
|
|
- data-suppress-pane-reveal → kills BOTH edges (use for small regions
|
|
like the thread timeline).
|
|
- data-suppress-pane-reveal-side → kills only the hovered region's OWN side
|
|
(side read from its [data-pane-side]
|
|
pane ancestor), so the opposite sidebar
|
|
stays summonable while you work a pane. */
|
|
[data-pane-shell]:has([data-suppress-pane-reveal]:hover) [data-pane-reveal-trigger] {
|
|
pointer-events: none;
|
|
}
|
|
|
|
[data-pane-shell]:has([data-pane-side='left'] [data-suppress-pane-reveal-side]:hover)
|
|
[data-pane-side='left']
|
|
[data-pane-reveal-trigger],
|
|
[data-pane-shell]:has([data-pane-side='right'] [data-suppress-pane-reveal-side]:hover)
|
|
[data-pane-side='right']
|
|
[data-pane-reveal-trigger] {
|
|
pointer-events: none;
|
|
}
|
|
|
|
:root:not([style*='--theme-asset-bg:']) .theme-default-filler {
|
|
display: block;
|
|
}
|
|
|
|
:root[style*='--theme-asset-bg:'] .theme-default-filler {
|
|
display: none;
|
|
}
|
|
|
|
/* Primitive-level pointer cursor for every interactive control (buttons,
|
|
selects, menu items, switches, tabs, summaries). Keeps individual
|
|
components from having to hardcode `cursor-pointer`; explicit cursor
|
|
utilities (cursor-grab, cursor-default, disabled:cursor-*) still win since
|
|
they live in the utilities layer. */
|
|
@layer base {
|
|
button:not(:disabled):not([aria-disabled='true']),
|
|
summary,
|
|
[role='button']:not([aria-disabled='true']),
|
|
[role='menuitem']:not([aria-disabled='true']),
|
|
[role='menuitemradio']:not([aria-disabled='true']),
|
|
[role='menuitemcheckbox']:not([aria-disabled='true']),
|
|
[role='option']:not([aria-disabled='true']),
|
|
[role='switch']:not([aria-disabled='true']),
|
|
[role='tab']:not([aria-disabled='true']) {
|
|
cursor: pointer;
|
|
}
|
|
}
|
|
|
|
@layer utilities {
|
|
[class*='rounded-full'],
|
|
[class*=':rounded-full'] {
|
|
border-radius: calc(var(--radius-scalar) * 9999rem);
|
|
}
|
|
}
|
|
|
|
@keyframes arc-border {
|
|
0% {
|
|
background-position: 15% 15%;
|
|
}
|
|
100% {
|
|
background-position: 75% 75%;
|
|
}
|
|
}
|
|
|
|
.arc-border {
|
|
--arc-c0: color-mix(in srgb, var(--dt-foreground) 0%, transparent);
|
|
--arc-c1: var(--dt-midground);
|
|
--arc-c2: var(--dt-background);
|
|
--arc-angle: 160deg;
|
|
--arc-width: 0.078125rem;
|
|
--arc-inset: -0.125rem;
|
|
--arc-duration: 2.23s;
|
|
|
|
pointer-events: none;
|
|
position: absolute;
|
|
overflow: hidden;
|
|
border-radius: inherit;
|
|
inset: var(--arc-inset);
|
|
padding: var(--arc-width);
|
|
mask:
|
|
linear-gradient(#000 0 0) content-box,
|
|
linear-gradient(#000 0 0);
|
|
-webkit-mask-composite: xor;
|
|
mask-composite: exclude;
|
|
}
|
|
|
|
:root.dark .arc-border {
|
|
--arc-c1: var(--dt-foreground);
|
|
}
|
|
|
|
/* Quest-style "needs you" pulse for a clarify-blocked session's dot —
|
|
a soft amber glow that breathes so the row draws the eye without a toast. */
|
|
@keyframes quest-glow {
|
|
0%,
|
|
100% {
|
|
transform: scale(1);
|
|
box-shadow:
|
|
0 0 0.1875rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 70%, transparent),
|
|
0 0 0.5rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 45%, transparent);
|
|
}
|
|
50% {
|
|
transform: scale(1.18);
|
|
box-shadow:
|
|
0 0 0.3125rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 90%, transparent),
|
|
0 0 0.875rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 65%, transparent);
|
|
}
|
|
}
|
|
|
|
.quest-glow {
|
|
animation: quest-glow 1.8s ease-in-out infinite;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.quest-glow {
|
|
animation: none;
|
|
box-shadow:
|
|
0 0 0.25rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 80%, transparent),
|
|
0 0 0.625rem color-mix(in srgb, var(--color-amber-500, #f59e0b) 55%, transparent);
|
|
}
|
|
}
|
|
|
|
/* Command-palette deep-link: briefly flash the targeted settings row. */
|
|
@keyframes setting-field-flash {
|
|
0% {
|
|
background-color: color-mix(in srgb, var(--dt-primary, #f59e0b) 22%, transparent);
|
|
}
|
|
100% {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
.setting-field-highlight {
|
|
animation: setting-field-flash 1.6s ease-out;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.setting-field-highlight {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
.arc-border::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
background: linear-gradient(
|
|
var(--arc-angle),
|
|
transparent 0%,
|
|
var(--arc-c0) 15%,
|
|
var(--arc-c1) 20%,
|
|
var(--arc-c2) 25%,
|
|
transparent 35%,
|
|
transparent 40%,
|
|
var(--arc-c0) 55%,
|
|
var(--arc-c1) 60%,
|
|
var(--arc-c2) 65%,
|
|
transparent 75%,
|
|
transparent 80%,
|
|
var(--arc-c0) 95%,
|
|
var(--arc-c1) 100%
|
|
);
|
|
background-size: 300% 300%;
|
|
animation: arc-border var(--arc-duration) linear infinite;
|
|
}
|
|
|
|
/* Flip the arc's travel direction (e.g. the Nous Portal hero row). */
|
|
.arc-border.arc-reverse::before {
|
|
animation-direction: reverse;
|
|
}
|
|
|
|
/* Nous Portal hero: slower, blue → orange arc. */
|
|
.arc-border.arc-nous,
|
|
:root.dark .arc-border.arc-nous {
|
|
--arc-c1: #4f8cff;
|
|
--arc-c2: #ff8c42;
|
|
--arc-duration: 3.27s;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.arc-border::before {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* No focus rings, anywhere. Kills the native outline plus Tailwind's
|
|
`focus-visible:ring-*` (a box-shadow driven by --tw-ring-*). Unlayered so it
|
|
beats the utilities layer without !important on the outline. The composer /
|
|
.desktop-input-chrome focus glow is untouched — those set `box-shadow`
|
|
directly rather than through the ring vars. */
|
|
*:focus,
|
|
*:focus-visible {
|
|
outline: none;
|
|
}
|
|
|
|
*:focus-visible {
|
|
--tw-ring-shadow: 0 0 #0000 !important;
|
|
--tw-ring-offset-shadow: 0 0 #0000 !important;
|
|
}
|
|
|
|
button {
|
|
-webkit-app-region: no-drag;
|
|
}
|
|
|
|
/* Button variant styling lives entirely in the cva in components/ui/button.tsx
|
|
(the single source of truth). Don't re-add [data-slot='button'] rules here —
|
|
attribute selectors out-specify the Tailwind utilities and silently override
|
|
the variants. */
|
|
|
|
[data-slot='dropdown-menu-content'],
|
|
[data-slot='select-content'],
|
|
[data-slot='dialog-content'] {
|
|
border-color: var(--ui-stroke-secondary);
|
|
background: color-mix(in srgb, var(--ui-bg-elevated) 96%, transparent);
|
|
box-shadow: var(--shadow-md);
|
|
backdrop-filter: blur(0.75rem) saturate(1.08);
|
|
-webkit-backdrop-filter: blur(0.75rem) saturate(1.08);
|
|
}
|
|
|
|
[data-slot='dropdown-menu-item']:focus,
|
|
[data-slot='dropdown-menu-checkbox-item']:focus,
|
|
[data-slot='dropdown-menu-radio-item']:focus {
|
|
background: var(--ui-bg-tertiary);
|
|
color: var(--ui-text-primary);
|
|
}
|
|
|
|
input,
|
|
textarea,
|
|
[contenteditable]:not([contenteditable='false']),
|
|
[data-slot='aui_user-message-root'],
|
|
[data-slot='aui_assistant-message-content'],
|
|
[data-slot='aui_system-message-root'],
|
|
[data-selectable-text='true'],
|
|
[data-selectable-text='true'] * {
|
|
-webkit-user-select: text;
|
|
user-select: text;
|
|
}
|
|
|
|
button,
|
|
[role='button'] {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
img,
|
|
picture,
|
|
video,
|
|
canvas,
|
|
svg {
|
|
-webkit-user-select: none;
|
|
user-select: none;
|
|
}
|
|
|
|
img,
|
|
video,
|
|
canvas {
|
|
-webkit-user-drag: none;
|
|
}
|
|
|
|
/* Tabs render 2-wide on every HTML code surface — the source preview, inline
|
|
diffs, markdown code blocks, tool output — so indentation matches the editor
|
|
and the terminal instead of the browser default (8). */
|
|
pre,
|
|
code {
|
|
tab-size: 2;
|
|
-moz-tab-size: 2;
|
|
}
|
|
|
|
/* Arc-style multicolor action surface (static, not animated). Reusable on any
|
|
Button via className. Unlayered so it beats Tailwind's bg-*/text-* variant
|
|
utilities. */
|
|
.btn-arc {
|
|
background-image: linear-gradient(110deg, #5b6cff 0%, #8b5cf6 28%, #d946ef 58%, #fb7185 82%, #fb923c 100%);
|
|
color: #fff;
|
|
border-color: transparent;
|
|
}
|
|
|
|
.btn-arc:hover:not(:disabled) {
|
|
filter: brightness(1.08) saturate(1.06);
|
|
}
|
|
|
|
/* Shared input chrome — mirrors composer hover/focus FX. Unlayered to beat Tailwind utilities. */
|
|
.desktop-input-chrome {
|
|
--ring-pct: 18%;
|
|
--ring-fall: var(--dt-input);
|
|
background: color-mix(in srgb, var(--dt-card) 68%, transparent);
|
|
border-color: color-mix(
|
|
in srgb,
|
|
var(--dt-composer-ring) calc(var(--ring-pct) * var(--composer-ring-strength)),
|
|
var(--ring-fall)
|
|
);
|
|
box-shadow: none;
|
|
transition:
|
|
background-color 200ms ease-out,
|
|
border-color 200ms ease-out;
|
|
}
|
|
|
|
.desktop-input-chrome:hover {
|
|
--ring-pct: 30%;
|
|
background: color-mix(in srgb, var(--dt-card) 86%, transparent);
|
|
}
|
|
|
|
.desktop-input-chrome:focus {
|
|
--ring-pct: 45%;
|
|
--ring-fall: transparent;
|
|
background: var(--dt-card);
|
|
box-shadow: none;
|
|
outline: none;
|
|
}
|
|
|
|
.desktop-input-chrome[aria-invalid='true'] {
|
|
border-color: var(--dt-destructive);
|
|
}
|
|
|
|
@layer components {
|
|
.scrollbar-dt,
|
|
.scrollbar-dt * {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in srgb, var(--dt-midground) 18%, transparent) transparent;
|
|
}
|
|
|
|
.scrollbar-dt::-webkit-scrollbar,
|
|
.scrollbar-dt *::-webkit-scrollbar {
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
}
|
|
|
|
.scrollbar-dt::-webkit-scrollbar-track,
|
|
.scrollbar-dt::-webkit-scrollbar-corner,
|
|
.scrollbar-dt *::-webkit-scrollbar-track,
|
|
.scrollbar-dt *::-webkit-scrollbar-corner {
|
|
background: transparent;
|
|
}
|
|
|
|
.scrollbar-dt::-webkit-scrollbar-thumb,
|
|
.scrollbar-dt *::-webkit-scrollbar-thumb {
|
|
background: color-mix(in srgb, var(--dt-midground) 18%, transparent);
|
|
border-radius: 9999rem;
|
|
border: 0.125rem solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.scrollbar-dt::-webkit-scrollbar-thumb:hover,
|
|
.scrollbar-dt *::-webkit-scrollbar-thumb:hover {
|
|
background: color-mix(in srgb, var(--dt-midground) 40%, transparent);
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.scrollbar-dt::-webkit-scrollbar-button,
|
|
.scrollbar-dt *::-webkit-scrollbar-button {
|
|
display: none;
|
|
}
|
|
|
|
/* Variant for portaled overlays (Radix DropdownMenu, Popover, etc.) that
|
|
render under document.body, outside the `.scrollbar-dt` scope on
|
|
#root. Same visual treatment, applied directly to the overlay
|
|
container so its (and only its) internal scrollbar is themed. */
|
|
.dt-portal-scrollbar {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: color-mix(in srgb, var(--dt-midground) 28%, transparent) transparent;
|
|
}
|
|
|
|
.dt-portal-scrollbar::-webkit-scrollbar {
|
|
width: 0.375rem;
|
|
height: 0.375rem;
|
|
}
|
|
|
|
.dt-portal-scrollbar::-webkit-scrollbar-track,
|
|
.dt-portal-scrollbar::-webkit-scrollbar-corner {
|
|
background: transparent;
|
|
}
|
|
|
|
.dt-portal-scrollbar::-webkit-scrollbar-thumb {
|
|
background: color-mix(in srgb, var(--dt-midground) 28%, transparent);
|
|
border-radius: 9999rem;
|
|
border: 0.0625rem solid transparent;
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.dt-portal-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
background: color-mix(in srgb, var(--dt-midground) 50%, transparent);
|
|
background-clip: padding-box;
|
|
}
|
|
|
|
.dt-portal-scrollbar::-webkit-scrollbar-button {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Bottom clearance lives on [data-slot='aui_composer-clearance'] —
|
|
virtualized items unmount, so :nth-last-child can't fire reliably. */
|
|
|
|
[data-slot='aui_assistant-message-content'] {
|
|
padding-left: var(--message-text-indent);
|
|
font-size: var(--conversation-text-font-size);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-root'] {
|
|
width: 100%;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md,
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(p, li, blockquote, table, pre) {
|
|
font-size: inherit;
|
|
}
|
|
|
|
/* Tailwind Typography sets `.prose :where(p) { margin: 1.25em }` (~16px). That
|
|
selector ties our `my-*` utility on specificity and wins on source order, so
|
|
paragraph spacing must be reclaimed here at higher specificity. One tight
|
|
top-margin (bottom zeroed to avoid doubling), first child reset to flush. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(p) {
|
|
margin-block: var(--paragraph-gap) 0;
|
|
}
|
|
|
|
/* Headings are section breaks, so they own a larger top gap than prose — a
|
|
leading `# title` (common in re-entered/delegated turns) no longer butts
|
|
against the block above. Top-owned + bottom snug; the first-child reset
|
|
below still flushes a leading heading. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(h1, h2, h3, h4) {
|
|
margin-block: 1rem 0.25rem;
|
|
}
|
|
|
|
/* First rendered element of a prose block is flush — the block-level gap above
|
|
(tool / paragraph) already provides the separation. Reach one level deep too:
|
|
Streamdown wraps blocks in a `div.space-y-*`, so the real first line is the
|
|
first child's first child. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md > :first-child,
|
|
[data-slot='aui_assistant-message-content'] .aui-md > :first-child > :first-child {
|
|
margin-top: 0;
|
|
}
|
|
|
|
/* Prose, tools, todos, and thinking all share one left edge (the message
|
|
content's --message-text-indent). No extra prose indent — a single gutter
|
|
reads cleaner than a ragged tool-vs-reply column. */
|
|
|
|
/* RTL/bidi chat text (#44150): each block resolves its own base direction from
|
|
its first strong char (UAX#9 plaintext). text-align:start makes that resolved
|
|
direction drive alignment too — load-bearing, since the user bubble pins
|
|
text-left. direction is never set, so chrome/layout/list-indent stay LTR (the
|
|
issue asks not to flip the whole UI). Covers assistant prose, user lines, and
|
|
both composers (main + edit share composer-rich-input). */
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(p, h1, h2, h3, h4, h5, h6, li, blockquote),
|
|
[data-slot='aui_user-inline-text'],
|
|
[data-slot='composer-rich-input'] {
|
|
unicode-bidi: plaintext;
|
|
text-align: start;
|
|
}
|
|
|
|
/* Inline code/KaTeX don't vote on direction and keep their own order: isolate
|
|
makes bidi treat each as one neutral, so a block that *starts* with `./run.sh`
|
|
then Arabic still resolves RTL, and the command's neutrals (dots/slashes)
|
|
aren't reordered by the surrounding RTL run. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(:not(pre) > code),
|
|
[data-slot='aui_user-inline-code'],
|
|
[data-slot='aui_assistant-message-content'] .aui-md .katex {
|
|
direction: ltr;
|
|
unicode-bidi: isolate;
|
|
}
|
|
|
|
/* Fenced code stays LTR even inside an RTL list item/blockquote — never mirrors. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-slot='code-card'],
|
|
[data-slot='aui_user-fence'] {
|
|
direction: ltr;
|
|
text-align: left;
|
|
}
|
|
|
|
[data-slot='aui_user-message-root'] {
|
|
top: var(--sticky-human-top);
|
|
}
|
|
|
|
[data-slot='aui_user-message-root'],
|
|
[data-slot='aui_edit-composer-root'] {
|
|
--human-msg-line-height: 1.3;
|
|
font-size: var(--conversation-text-font-size);
|
|
}
|
|
|
|
[data-slot='aui_user-inline-text'] {
|
|
line-height: var(--human-msg-line-height);
|
|
}
|
|
|
|
[data-slot='aui_edit-composer-root'] [data-slot='composer-rich-input'] {
|
|
line-height: var(--human-msg-line-height);
|
|
}
|
|
|
|
/* Sticky human bubbles clamp to ~2 lines with a soft bottom fade so a long
|
|
prompt doesn't dominate the viewport. The clamp lifts only in the edit
|
|
composer; expanding on read-only :focus-within ran on mousedown (before the
|
|
swap) and fought stick-to-bottom when parked at the bottom. */
|
|
.sticky-human-clamp {
|
|
cursor: pointer;
|
|
max-height: calc(4 * var(--human-msg-line-height) * var(--conversation-text-font-size) + 0.15rem);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sticky-human-clamp[data-clamped='true'] {
|
|
-webkit-mask-image: linear-gradient(to bottom, #000 55%, transparent);
|
|
mask-image: linear-gradient(to bottom, #000 55%, transparent);
|
|
}
|
|
|
|
/* Stick-to-bottom owns scrollTop while following. Once escaped, native anchoring
|
|
is safe and keeps sticky human edits from shoving the viewport; data-editing
|
|
enables that path before React swaps in the inline editor. */
|
|
[data-slot='aui_thread-viewport'] {
|
|
overflow-anchor: none;
|
|
}
|
|
|
|
[data-slot='aui_thread-viewport'][data-following='false'],
|
|
[data-slot='aui_thread-viewport'][data-editing='true'] {
|
|
overflow-anchor: auto;
|
|
}
|
|
|
|
[data-slot='aui_thread-content'] {
|
|
max-width: var(--composer-width);
|
|
padding-inline: 1.5rem;
|
|
}
|
|
|
|
[data-slot='aui_intro'] {
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding-bottom: var(--composer-measured-height);
|
|
text-align: center;
|
|
}
|
|
|
|
[data-slot='aui_intro'] > div {
|
|
max-width: min(var(--composer-width), 82vw);
|
|
}
|
|
|
|
[data-slot='aui_intro'] p:last-child {
|
|
max-width: 34rem;
|
|
margin-inline: auto;
|
|
color: var(--ui-text-tertiary);
|
|
font-size: 0.875rem;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.fit-text {
|
|
--fit-captured-length: initial;
|
|
--fit-support-sentinel: var(--fit-captured-length, 9999px);
|
|
|
|
display: flex;
|
|
container-type: inline-size;
|
|
}
|
|
|
|
.fit-text > [aria-hidden] {
|
|
visibility: hidden;
|
|
}
|
|
|
|
.fit-text > :not([aria-hidden]) {
|
|
flex-grow: 1;
|
|
container-type: inline-size;
|
|
|
|
--fit-captured-length: 100cqi;
|
|
--fit-available-space: var(--fit-captured-length);
|
|
}
|
|
|
|
.fit-text > :not([aria-hidden]) > * {
|
|
--fit-support-sentinel: inherit;
|
|
--fit-captured-length: 100cqi;
|
|
--fit-ratio: tan(atan2(var(--fit-available-space), var(--fit-available-space) - var(--fit-captured-length)));
|
|
|
|
display: block;
|
|
inline-size: var(--fit-available-space);
|
|
font-size: clamp(
|
|
var(--fit-min, 1em),
|
|
1em * var(--fit-ratio),
|
|
var(--fit-max, infinity * 1px) - var(--fit-support-sentinel)
|
|
);
|
|
}
|
|
|
|
@container (inline-size > 0) {
|
|
.fit-text > :not([aria-hidden]) > * {
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
@property --fit-captured-length {
|
|
syntax: '<length>';
|
|
initial-value: 0px;
|
|
inherits: true;
|
|
}
|
|
|
|
[data-slot='composer-root'] {
|
|
/* +10px width compensates the 5px side padding so the visible surface keeps
|
|
its exact width/position — the inline padding is just transparent grab space
|
|
for the peel-out drag, matching the floating composer's 5px platform. */
|
|
width: calc(min(var(--composer-width), calc(100% - 2rem)) + 10px);
|
|
padding-inline: 5px;
|
|
padding-bottom: var(--composer-shell-pad-block-end);
|
|
}
|
|
|
|
/* Popped-out (floating) composer: compact width + an even 5px transparent grab
|
|
platform. The higher-specificity selector resets the base rule's padding-bottom
|
|
so the inset is equal on all four sides (not 5px sides / shell-pad bottom). */
|
|
[data-slot='composer-root'][data-popped-out] {
|
|
width: var(--composer-popout-width, 24rem);
|
|
max-width: calc(100vw - 1.5rem);
|
|
padding: 5px;
|
|
}
|
|
|
|
/* Dock glow intensity scale — dimmer in light mode (the primary glow reads
|
|
much stronger over a light backdrop), full strength in dark mode. */
|
|
:root {
|
|
--dock-glow-scale: 0.55;
|
|
}
|
|
|
|
.dark {
|
|
--dock-glow-scale: 1;
|
|
}
|
|
|
|
/* Drag-region hatch — a diagonal ///// pattern (Photoshop-style) that fades into
|
|
the transparent grab margin on hover (and stays while dragging) to signal the
|
|
composer is draggable. Inherits the root radius so it clips to the corners. */
|
|
[data-slot='composer-drag-region'] {
|
|
/* Hatch frame radius (tuned by hand). */
|
|
border-radius: 0.4rem;
|
|
opacity: 0;
|
|
transition: opacity 150ms ease;
|
|
background-image: repeating-linear-gradient(
|
|
-45deg,
|
|
color-mix(in srgb, var(--ui-text-tertiary) 38%, transparent) 0,
|
|
color-mix(in srgb, var(--ui-text-tertiary) 38%, transparent) 1px,
|
|
transparent 1px,
|
|
transparent 3.5px
|
|
);
|
|
}
|
|
|
|
[data-slot='composer-drag-region']:hover,
|
|
[data-slot='composer-drag-region'][data-dragging] {
|
|
opacity: 0.33;
|
|
}
|
|
|
|
[data-slot='composer-root'] > .pointer-events-none {
|
|
background: linear-gradient(
|
|
to bottom,
|
|
transparent,
|
|
color-mix(in srgb, var(--ui-chat-surface-background) 88%, transparent)
|
|
) !important;
|
|
}
|
|
|
|
[data-slot='composer-surface'] {
|
|
border-color: var(--ui-stroke-secondary) !important;
|
|
}
|
|
|
|
[data-slot='composer-fade'] {
|
|
min-height: 2.375rem;
|
|
}
|
|
|
|
[data-slot='composer-rich-input'] {
|
|
color: var(--ui-text-primary);
|
|
font-size: 0.8125rem;
|
|
}
|
|
|
|
[data-slot='composer-rich-input']:empty::before {
|
|
color: var(--ui-text-tertiary) !important;
|
|
}
|
|
|
|
/* ── Composer fill — ONE var painted by the surface AND anything docked to it
|
|
(slash·@ popover, `?` help). State ladder sets the var; consumers just paint
|
|
`background: var(--composer-fill)`, so every state matches by construction.
|
|
The :has() rule is last on purpose: while a completion drawer is open it
|
|
beats focus/scroll and forces an OPAQUE fill (both mix endpoints solid) —
|
|
translucent glass can never match across the two layers because they sample
|
|
different backdrops. */
|
|
:root {
|
|
/* Fallback for drawers outside the main composer (e.g. edit-message). */
|
|
--composer-fill: color-mix(in srgb, var(--dt-card) 90%, var(--dt-background));
|
|
}
|
|
|
|
[data-slot='composer-root'] {
|
|
--composer-fill: color-mix(in srgb, var(--dt-card) 72%, transparent);
|
|
}
|
|
|
|
[data-slot='composer-root'][data-thread-scrolled-up] {
|
|
--composer-fill: color-mix(in srgb, var(--dt-card) 48%, transparent);
|
|
}
|
|
|
|
/* Tool/thinking blocks now live at message-text alignment (no leading
|
|
chevron column to escape into), so their headers and bodies share a
|
|
common left edge with the model's text. */
|
|
[data-slot='aui_assistant-message-content'] > [data-slot='tool-block'],
|
|
[data-slot='aui_assistant-message-content'] > [data-slot='aui_thinking-disclosure'] {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-streamdown='code-block'] code {
|
|
max-width: none;
|
|
font-family: inherit;
|
|
font-size: inherit;
|
|
padding: 0;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
color: inherit;
|
|
overflow-x: visible;
|
|
overflow-wrap: inherit;
|
|
vertical-align: baseline;
|
|
word-break: inherit;
|
|
white-space: inherit;
|
|
}
|
|
|
|
/* Streamdown's adapter wraps code fences in a `data-streamdown="code-block"`
|
|
container with its own card chrome. We render our own <CodeCard>, so this
|
|
strips the upstream chrome down to a layout-only passthrough. */
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-streamdown='code-block'] {
|
|
contain: none;
|
|
overflow: visible;
|
|
margin-block: var(--paragraph-gap) 0 !important;
|
|
padding: 0 !important;
|
|
gap: 0 !important;
|
|
border: 0 !important;
|
|
border-radius: 0 !important;
|
|
background: transparent !important;
|
|
color: inherit;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-streamdown='code-block']:has(.aui-prose-fence) {
|
|
margin-block: 0 !important;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-slot='code-card'] {
|
|
/* Streamdown nests blocks, so the container's child-combinator rhythm can't
|
|
reach the card. Carry the paragraph gap on the card itself (top-owned);
|
|
collapses cleanly with the wrapper's margin when one is present, and the
|
|
first-child reset still flushes a leading code block. */
|
|
margin-block: var(--paragraph-gap) 0;
|
|
position: relative;
|
|
transition:
|
|
border-color 180ms ease-out,
|
|
box-shadow 180ms ease-out,
|
|
background-color 180ms ease-out;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-slot='code-card'][data-streaming='true'] {
|
|
animation:
|
|
code-card-stream-enter 180ms cubic-bezier(0.16, 1, 0.3, 1) both,
|
|
code-card-stream-glow 1.8s ease-in-out 180ms infinite alternate;
|
|
border-color: color-mix(in srgb, var(--dt-ring) 24%, var(--ui-stroke-tertiary));
|
|
box-shadow:
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 10%, transparent),
|
|
0 0.625rem 1.75rem color-mix(in srgb, var(--dt-ring) 8%, transparent);
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content']
|
|
.aui-md
|
|
[data-slot='code-card'][data-streaming='true']
|
|
[data-slot='code-card-body'] {
|
|
-webkit-mask-image: linear-gradient(to bottom, black 0%, black calc(100% - 1.5rem), rgb(0 0 0 / 64%) 100%);
|
|
mask-image: linear-gradient(to bottom, black 0%, black calc(100% - 1.5rem), rgb(0 0 0 / 64%) 100%);
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md :not(pre) > code {
|
|
background: var(--ui-inline-code-background);
|
|
color: var(--ui-inline-code-foreground);
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md :where(.aui-shiki, .aui-shiki > pre) {
|
|
margin: 0 !important;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table {
|
|
border-spacing: 0;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table > table,
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table thead,
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table tbody,
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table tr,
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table th,
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table td {
|
|
margin: 0 !important;
|
|
margin-block-start: 0 !important;
|
|
margin-block-end: 0 !important;
|
|
}
|
|
|
|
[data-slot='aui_assistant-message-content'] .aui-md .aui-md-table thead {
|
|
border-bottom-color: var(--dt-border) !important;
|
|
}
|
|
|
|
/* Tool / thinking blocks are scaffolding around the model's reply, so we
|
|
keep them transparent and fade them slightly. The reading column (prose)
|
|
stays at full strength; scaffolding lifts back to full opacity on
|
|
hover/focus so it stays legible when the user actually wants to read it. */
|
|
[data-slot='tool-block'],
|
|
[data-slot='aui_thinking-disclosure'] {
|
|
background: transparent !important;
|
|
}
|
|
|
|
/* Fade scaffolding so the prose reading column stays primary. Two targets:
|
|
a thinking disclosure fades as one block, and each *individual* tool row
|
|
(`[data-tool-row]`) fades on its own. We deliberately do NOT fade the tool
|
|
group wrapper (`[data-tool-group]`): opacity on a parent opens a stacking
|
|
context, so a child row can never be more opaque than the group — that made
|
|
it impossible to keep one row lit (an open diff) while its siblings faded.
|
|
With the fade per-row, each row hovers/focuses independently. */
|
|
[data-slot='aui_assistant-message-content'] > [data-slot='aui_thinking-disclosure'],
|
|
[data-slot='aui_assistant-message-content'] [data-slot='tool-block'][data-tool-row] {
|
|
opacity: 0.67;
|
|
transition: opacity 120ms ease-out;
|
|
}
|
|
|
|
/* Lift on hover or *keyboard* focus only. `:focus-within` also matches the
|
|
focus a mouse click leaves on the disclosure toggle, which kept a row lit
|
|
after you clicked to collapse it; `:has(:focus-visible)` excludes that. */
|
|
[data-slot='aui_assistant-message-content'] > [data-slot='aui_thinking-disclosure']:is(:hover, :has(:focus-visible)),
|
|
[data-slot='aui_assistant-message-content'] [data-slot='tool-block'][data-tool-row]:is(:hover, :has(:focus-visible)) {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Shiki surfaces in the inline file diff + source preview: strip the theme's
|
|
own background/margins, and lay each `.line` on its own grid row so the
|
|
inter-line `\n` text nodes can't double-space full-width rows. Empty lines
|
|
carry a non-breaking space so blank rows keep their height. */
|
|
[data-slot='file-diff-panel'] .shiki,
|
|
[data-slot='file-diff-panel'] .shiki code,
|
|
.preview-source-code .shiki,
|
|
.preview-source-code .shiki code {
|
|
margin: 0;
|
|
background: transparent !important;
|
|
}
|
|
|
|
[data-slot='file-diff-panel'] .shiki code,
|
|
.preview-source-code .shiki code {
|
|
display: grid;
|
|
}
|
|
|
|
[data-slot='file-diff-panel'] .shiki code .line:empty::before,
|
|
.preview-source-code .shiki code .line:empty::before {
|
|
content: '\00a0';
|
|
}
|
|
|
|
/* Inline diff rows keep their utility-class padding; just floor the height. */
|
|
[data-slot='file-diff-panel'] .shiki code .line {
|
|
min-height: 1.25rem;
|
|
white-space: pre;
|
|
}
|
|
|
|
/* Source rows are a fixed editor-height box so source⇄diff toggling never
|
|
shifts and the gutter stays aligned. */
|
|
.preview-source-code .shiki code {
|
|
min-width: max-content;
|
|
}
|
|
|
|
.preview-source-code .shiki code .line {
|
|
display: block;
|
|
height: 1.25rem;
|
|
padding: 0 0.625rem;
|
|
line-height: 1.25rem;
|
|
white-space: pre;
|
|
}
|
|
|
|
/* The github-dark token palette reads candy-bright at our small code size.
|
|
`github-dark-dimmed` only dims the *background* (which we strip), so soften
|
|
the token *foregrounds* directly — a small saturation + brightness pullback,
|
|
hues preserved — for both code blocks and inline diffs. Dark mode only. */
|
|
.dark .shiki {
|
|
filter: saturate(0.82) brightness(0.92);
|
|
}
|
|
|
|
/* File edits (write_file / edit_file / patch) are the deliverable, not
|
|
scaffolding — the diff is what the user reviews, like a PR. An *expanded*
|
|
edit stays at full strength; collapsed it fades like any other row. The
|
|
`data-file-edit` marker sits on the same row element and is only present
|
|
while the row is open. */
|
|
[data-slot='aui_assistant-message-content'] [data-slot='tool-block'][data-tool-row][data-file-edit] {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Conversation block rhythm. assistant-ui renders each range as a direct child
|
|
of the message content with no per-part wrapper, so adjacency rules cover
|
|
every pairing — first block needs no reset, nested tool rows are untouched.
|
|
Two tiers: scaffolding (tool / thinking) gets a roomy block gap so it reads
|
|
as separate from the reply; consecutive prose collapses to a tight paragraph
|
|
rhythm so split-out text parts don't look like a big gap. */
|
|
/* Scaffolding adjacent to anything → roomy block gap. */
|
|
[data-slot='aui_assistant-message-content']
|
|
> :is([data-slot='tool-block'], [data-slot='aui_thinking-disclosure'])
|
|
+ :is([data-slot='tool-block'], [data-slot='aui_thinking-disclosure'], .aui-md),
|
|
[data-slot='aui_assistant-message-content']
|
|
> .aui-md
|
|
+ :is([data-slot='tool-block'], [data-slot='aui_thinking-disclosure']) {
|
|
margin-top: var(--turn-block-gap);
|
|
}
|
|
|
|
/* Prose ↔ prose → tight paragraph rhythm, matching in-block paragraph spacing. */
|
|
[data-slot='aui_assistant-message-content'] > .aui-md + .aui-md {
|
|
margin-top: var(--paragraph-gap);
|
|
}
|
|
|
|
/* Message action bars — flat icon hits with default dim; only the hovered/focused control is full-strength. */
|
|
[data-slot='aui_msg-actions'] button {
|
|
border: 0;
|
|
border-radius: 0;
|
|
background: transparent;
|
|
box-shadow: none;
|
|
padding: 0;
|
|
gap: 0;
|
|
height: auto;
|
|
width: auto;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
flex-shrink: 0;
|
|
cursor: pointer;
|
|
color: var(--color-muted-foreground);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
[data-slot='aui_msg-actions'] button:disabled {
|
|
cursor: default;
|
|
}
|
|
|
|
[data-slot='aui_msg-actions'] button:hover {
|
|
background: transparent;
|
|
color: var(--color-foreground);
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-slot='aui_msg-actions'] button:active {
|
|
background: transparent;
|
|
}
|
|
|
|
[data-slot='aui_msg-actions'] button:focus-visible {
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-slot='aui_msg-actions'] button svg {
|
|
width: 0.875rem;
|
|
height: 0.875rem;
|
|
}
|
|
|
|
/* Live thinking preview window. Pairs with the ResizeObserver in
|
|
ThinkingDisclosure that pins scrollTop to the bottom — older lines fade
|
|
into the top mask while the latest tokens settle in below. */
|
|
.thinking-preview {
|
|
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 28%, black 100%);
|
|
mask-image: linear-gradient(to bottom, transparent 0%, black 28%, black 100%);
|
|
}
|
|
|
|
@keyframes code-card-stream-enter {
|
|
from {
|
|
opacity: 0.74;
|
|
transform: translateY(0.375rem);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Floating "jump to bottom" control (see scroll-to-bottom-button.tsx).
|
|
Directional scale: it contracts toward 1 as it arrives (from 1.1) and keeps
|
|
contracting to 0.9 as it leaves — always shrinking in the direction of
|
|
travel, so the motion reads as a soft settle / recede rather than a pop. The
|
|
X half-offset stays baked into every transform so `left-1/2` centering holds
|
|
through the animation. */
|
|
.thread-jump-button {
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(0.3rem) scale(0.9);
|
|
}
|
|
|
|
.thread-jump-button[data-state='in'] {
|
|
animation: thread-jump-in 200ms cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
|
}
|
|
|
|
.thread-jump-button[data-state='out'] {
|
|
animation: thread-jump-out 180ms ease-in forwards;
|
|
}
|
|
|
|
@keyframes thread-jump-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(0.3rem) scale(1.1);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes thread-jump-out {
|
|
from {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0) scale(1);
|
|
}
|
|
|
|
to {
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(0.3rem) scale(0.9);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.thread-jump-button[data-state='in'],
|
|
.thread-jump-button[data-state='out'] {
|
|
animation: none;
|
|
transition: opacity 120ms linear;
|
|
}
|
|
|
|
.thread-jump-button[data-state='in'] {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes code-card-stream-glow {
|
|
from {
|
|
border-color: color-mix(in srgb, var(--dt-ring) 18%, var(--ui-stroke-tertiary));
|
|
box-shadow:
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 6%, transparent),
|
|
0 0.5rem 1.5rem color-mix(in srgb, var(--dt-ring) 5%, transparent);
|
|
}
|
|
|
|
to {
|
|
border-color: color-mix(in srgb, var(--dt-ring) 32%, var(--ui-stroke-tertiary));
|
|
box-shadow:
|
|
0 0 0 0.0625rem color-mix(in srgb, var(--dt-ring) 12%, transparent),
|
|
0 0.75rem 2rem color-mix(in srgb, var(--dt-ring) 10%, transparent);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
[data-slot='aui_assistant-message-content'] .aui-md [data-slot='code-card'][data-streaming='true'] {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Pet egg hatch (Cmd-K → Pets → Generate) */
|
|
/* The incubation wobble + reveal flash/pop give the draft→pet step a */
|
|
/* Pokémon-style "egg is hatching" beat instead of a bare spinner. */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
.pet-egg {
|
|
position: relative;
|
|
width: 5.5rem;
|
|
height: 7rem;
|
|
border-radius: 50% 50% 50% 50% / 62% 62% 38% 38%;
|
|
background:
|
|
radial-gradient(120% 90% at 32% 26%, color-mix(in srgb, var(--ui-accent) 14%, #fff) 0%, #f4ecd8 46%, #e4d3ad 100%);
|
|
box-shadow:
|
|
inset -0.45rem -0.6rem 1.1rem color-mix(in srgb, #000 16%, transparent),
|
|
inset 0.35rem 0.4rem 0.7rem color-mix(in srgb, #fff 70%, transparent),
|
|
0 0.4rem 0.9rem color-mix(in srgb, #000 22%, transparent);
|
|
transform-origin: 50% 88%;
|
|
animation: pet-egg-wobble 2.4s ease-in-out infinite;
|
|
}
|
|
|
|
/* Compact egg (empty-state hero). Children are %-based so they track the size;
|
|
only the rem box-shadow needs scaling down to stay crisp. */
|
|
.pet-egg--sm {
|
|
width: 3.25rem;
|
|
height: 4.1rem;
|
|
box-shadow:
|
|
inset -0.28rem -0.38rem 0.7rem color-mix(in srgb, #000 16%, transparent),
|
|
inset 0.22rem 0.26rem 0.45rem color-mix(in srgb, #fff 70%, transparent),
|
|
0 0.25rem 0.55rem color-mix(in srgb, #000 22%, transparent);
|
|
}
|
|
|
|
.pet-egg__shine {
|
|
position: absolute;
|
|
top: 14%;
|
|
left: 22%;
|
|
width: 28%;
|
|
height: 22%;
|
|
border-radius: 50%;
|
|
background: color-mix(in srgb, #fff 85%, transparent);
|
|
filter: blur(2px);
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.pet-egg__spot {
|
|
position: absolute;
|
|
border-radius: 50%;
|
|
background: color-mix(in srgb, var(--ui-accent) 70%, #b89b63);
|
|
opacity: 0.55;
|
|
}
|
|
|
|
.pet-egg__glow {
|
|
position: absolute;
|
|
inset: -35%;
|
|
border-radius: 50%;
|
|
background: radial-gradient(circle, color-mix(in srgb, var(--ui-accent) 55%, transparent) 0%, transparent 62%);
|
|
animation: pet-egg-glow 2.4s ease-in-out infinite;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.pet-egg-shadow {
|
|
width: 4.5rem;
|
|
height: 0.8rem;
|
|
border-radius: 50%;
|
|
/* Lighter on light backgrounds (~20% less ink); dark mode keeps it grounded. */
|
|
background: radial-gradient(circle, color-mix(in srgb, #000 var(--pet-egg-shadow-ink, 26%), transparent) 0%, transparent 72%);
|
|
animation: pet-egg-shadow 2.4s ease-in-out infinite;
|
|
}
|
|
|
|
.dark .pet-egg-shadow {
|
|
--pet-egg-shadow-ink: 32%;
|
|
}
|
|
|
|
/* Contact shadow sized for the compact incubator egg (roughly its footprint). */
|
|
.pet-egg-shadow--sm {
|
|
width: 3rem;
|
|
height: 0.6rem;
|
|
}
|
|
|
|
/* Contact shadow under the revealed pet — mirrors the floating mascot's in-app
|
|
shadow: an ellipse at the feet, ~55% of the sprite width, sitting behind it. */
|
|
.pet-contact-shadow {
|
|
position: absolute;
|
|
bottom: -0.15rem;
|
|
left: 50%;
|
|
width: 55%;
|
|
aspect-ratio: 100 / 28;
|
|
transform: translateX(-50%);
|
|
background: radial-gradient(ellipse at center, color-mix(in srgb, #000 42%, transparent) 0%, transparent 70%);
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
/* Hatch wiggle for the pixel egg (rocks around its base). */
|
|
.pet-wobble {
|
|
transform-origin: 50% 85%;
|
|
animation: pet-egg-wobble 2.4s ease-in-out infinite;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.pet-wobble {
|
|
animation: none;
|
|
}
|
|
}
|
|
|
|
@keyframes pet-egg-wobble {
|
|
0%,
|
|
62%,
|
|
100% {
|
|
transform: rotate(0deg);
|
|
}
|
|
8% {
|
|
transform: rotate(-7deg);
|
|
}
|
|
16% {
|
|
transform: rotate(6deg);
|
|
}
|
|
24% {
|
|
transform: rotate(-5deg);
|
|
}
|
|
32% {
|
|
transform: rotate(4deg);
|
|
}
|
|
40% {
|
|
transform: rotate(0deg);
|
|
}
|
|
/* the "almost out" burst */
|
|
70% {
|
|
transform: rotate(-12deg);
|
|
}
|
|
76% {
|
|
transform: rotate(12deg);
|
|
}
|
|
82% {
|
|
transform: rotate(-9deg);
|
|
}
|
|
88% {
|
|
transform: rotate(7deg);
|
|
}
|
|
94% {
|
|
transform: rotate(-3deg);
|
|
}
|
|
}
|
|
|
|
@keyframes pet-egg-glow {
|
|
0%,
|
|
100% {
|
|
opacity: 0.35;
|
|
transform: scale(0.92);
|
|
}
|
|
70% {
|
|
opacity: 0.4;
|
|
}
|
|
84% {
|
|
opacity: 0.85;
|
|
transform: scale(1.08);
|
|
}
|
|
}
|
|
|
|
@keyframes pet-egg-shadow {
|
|
0%,
|
|
62%,
|
|
100% {
|
|
transform: scaleX(1);
|
|
opacity: 0.6;
|
|
}
|
|
76% {
|
|
transform: scaleX(0.8);
|
|
opacity: 0.45;
|
|
}
|
|
}
|
|
|
|
.pet-reveal {
|
|
animation: pet-reveal-pop 620ms cubic-bezier(0.22, 1.4, 0.4, 1) both;
|
|
}
|
|
|
|
@keyframes pet-reveal-pop {
|
|
0% {
|
|
opacity: 0;
|
|
transform: scale(0.35) translateY(0.4rem);
|
|
}
|
|
60% {
|
|
opacity: 1;
|
|
transform: scale(1.12) translateY(0);
|
|
}
|
|
100% {
|
|
transform: scale(1) translateY(0);
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.pet-egg,
|
|
.pet-egg__glow,
|
|
.pet-egg-shadow,
|
|
.pet-reveal {
|
|
animation: none;
|
|
}
|
|
.pet-reveal {
|
|
opacity: 1;
|
|
transform: none;
|
|
}
|
|
}
|
|
|
|
/* Pet generation progress bar — determinate (hatch rows: done/total) or */
|
|
/* indeterminate (drafts, which return together so a % would just snap). */
|
|
.pet-progress {
|
|
position: relative;
|
|
height: 0.25rem;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
border-radius: 9999px;
|
|
background: color-mix(in srgb, var(--ui-accent) 15%, transparent);
|
|
}
|
|
|
|
.pet-progress__fill {
|
|
position: absolute;
|
|
inset: 0 auto 0 0;
|
|
height: 100%;
|
|
border-radius: 9999px;
|
|
background: var(--ui-accent);
|
|
transition: width 320ms ease;
|
|
}
|
|
|
|
.pet-progress__indeterminate {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 40%;
|
|
border-radius: 9999px;
|
|
background: var(--ui-accent);
|
|
animation: pet-progress-slide 1.15s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes pet-progress-slide {
|
|
0% {
|
|
left: -42%;
|
|
}
|
|
100% {
|
|
left: 100%;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.pet-progress__indeterminate {
|
|
animation: none;
|
|
left: 0;
|
|
width: 100%;
|
|
opacity: 0.4;
|
|
}
|
|
}
|