mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 11:53:10 +08:00
2779 lines
85 KiB
CSS
2779 lines
85 KiB
CSS
@import "tailwindcss";
|
||
@plugin "@tailwindcss/typography";
|
||
|
||
/* ─── Dark variant binding ────────────────────────────────────────
|
||
The app has six themes switched via `<html data-theme>`. Following the OS
|
||
is resolved in JS (src/theme/systemAppearance.ts) and lands on that same
|
||
attribute, so `data-theme` stays the single source of truth here. Without
|
||
this binding, Tailwind's default `dark:` compiles to
|
||
`prefers-color-scheme: dark` and would fire on the wrong theme — right by
|
||
accident for someone following the system, plainly wrong for anyone on a
|
||
fixed palette.
|
||
|
||
Two of the six sit on a dark ground — `dark` and `ink-blue` — so both are
|
||
listed. Binding only `dark` is how a `dark:` utility would silently render
|
||
its light branch on ink-blue. */
|
||
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *, [data-theme="ink-blue"], [data-theme="ink-blue"] *));
|
||
|
||
/* ─── Self-hosted fonts (no Google CDN dependency) ────────────── */
|
||
|
||
/* Inter – variable weight 400-600, latin */
|
||
@font-face {
|
||
font-family: 'Inter';
|
||
font-style: normal;
|
||
font-weight: 400 600;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/inter-latin.woff2') format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: 'Inter';
|
||
font-style: normal;
|
||
font-weight: 400 600;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/inter-latin-ext.woff2') format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
|
||
/* Manrope – variable weight 400-800, latin */
|
||
@font-face {
|
||
font-family: 'Manrope';
|
||
font-style: normal;
|
||
font-weight: 400 800;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/manrope-latin.woff2') format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: 'Manrope';
|
||
font-style: normal;
|
||
font-weight: 400 800;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/manrope-latin-ext.woff2') format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
|
||
/* JetBrains Mono – weight 400, latin */
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/jetbrains-mono-latin.woff2') format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: 'JetBrains Mono';
|
||
font-style: normal;
|
||
font-weight: 400;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/jetbrains-mono-latin-ext.woff2') format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
|
||
/* Noto Serif SC – variable weight 600-900, latin only.
|
||
The serif headings of 「纸 · 墨 · 印」 are the "墨" half of the identity, but
|
||
the CJK cuts of this family run ~6MB per weight. Only the latin subset is
|
||
self-hosted (43KB for all three weights); Chinese glyphs fall through to the
|
||
platform serif named in `--font-headline`, which is what the handoff
|
||
prototype itself falls back to. */
|
||
@font-face {
|
||
font-family: 'Noto Serif SC';
|
||
font-style: normal;
|
||
font-weight: 600 900;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/noto-serif-sc-latin.woff2') format('woff2');
|
||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||
}
|
||
@font-face {
|
||
font-family: 'Noto Serif SC';
|
||
font-style: normal;
|
||
font-weight: 600 900;
|
||
font-display: swap;
|
||
src: url('../../public/fonts/noto-serif-sc-latin-ext.woff2') format('woff2');
|
||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||
}
|
||
|
||
/* Material Symbols Outlined – variable weight, use block to avoid showing raw text */
|
||
@font-face {
|
||
font-family: 'Material Symbols Outlined';
|
||
font-style: normal;
|
||
font-weight: 100 700;
|
||
font-display: block;
|
||
src: url('../../public/fonts/material-symbols-outlined.woff2') format('woff2');
|
||
}
|
||
|
||
.code-viewer-area .line,
|
||
.code-viewer-area .token-line {
|
||
display: block;
|
||
}
|
||
|
||
.code-viewer-area .line {
|
||
padding: 0 12px;
|
||
}
|
||
|
||
.code-viewer-area .has-line-numbers .line {
|
||
padding-left: 12px;
|
||
}
|
||
|
||
.code-viewer-area .line:hover,
|
||
.code-viewer-area .token-line:hover {
|
||
background: var(--color-surface-hover);
|
||
}
|
||
|
||
.code-viewer-area .has-line-numbers,
|
||
.code-viewer-area .rs-has-line-numbers {
|
||
counter-reset: line-number calc(var(--line-start, 1) - 1);
|
||
--line-numbers-foreground: var(--color-text-tertiary);
|
||
--line-numbers-width: 2.5ch;
|
||
--line-numbers-padding-left: 0;
|
||
--line-numbers-padding-right: 0;
|
||
--line-numbers-font-size: 11px;
|
||
--rs-line-numbers-foreground: var(--line-numbers-foreground);
|
||
--rs-line-numbers-width: var(--line-numbers-width);
|
||
--rs-line-numbers-padding-left: var(--line-numbers-padding-left);
|
||
--rs-line-numbers-padding-right: var(--line-numbers-padding-right);
|
||
--rs-line-numbers-font-size: var(--line-numbers-font-size);
|
||
}
|
||
|
||
.code-viewer-area .line-numbers::before,
|
||
.code-viewer-area .rs-line-number::before {
|
||
content: counter(line-number);
|
||
counter-increment: line-number;
|
||
display: inline-flex;
|
||
justify-content: flex-end;
|
||
box-sizing: content-box;
|
||
min-width: var(--rs-line-numbers-width);
|
||
padding-left: var(--rs-line-numbers-padding-left);
|
||
padding-right: var(--rs-line-numbers-padding-right);
|
||
color: var(--rs-line-numbers-foreground);
|
||
font-size: var(--rs-line-numbers-font-size);
|
||
user-select: none;
|
||
pointer-events: none;
|
||
margin-right: 1.5ch;
|
||
}
|
||
|
||
.settings-terminal-host .xterm {
|
||
height: 100%;
|
||
padding: 0;
|
||
}
|
||
|
||
.settings-terminal-host .xterm-viewport {
|
||
scrollbar-color: var(--color-outline-a72) transparent;
|
||
}
|
||
|
||
.settings-terminal-host .xterm-screen {
|
||
outline: none;
|
||
}
|
||
|
||
.settings-terminal-host .xterm-accessibility:not(.debug),
|
||
.settings-terminal-host .xterm-message {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 10;
|
||
color: transparent;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.settings-terminal-host .xterm-accessibility-tree {
|
||
color: transparent;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.settings-terminal-host .xterm-helper-textarea {
|
||
position: absolute;
|
||
top: 0;
|
||
left: -9999em;
|
||
z-index: -5;
|
||
width: 0;
|
||
height: 0;
|
||
margin: 0;
|
||
border: 0;
|
||
padding: 0;
|
||
opacity: 0;
|
||
overflow: hidden;
|
||
resize: none;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.settings-checkbox-input {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 1;
|
||
width: 100%;
|
||
height: 100%;
|
||
margin: 0;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
}
|
||
|
||
.settings-checkbox-input:disabled {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.settings-zoom-kbd {
|
||
min-width: 18px;
|
||
height: 18px;
|
||
padding: 0 5px;
|
||
border: 1px solid var(--color-border);
|
||
border-radius: 5px;
|
||
background: var(--color-surface-container-lowest);
|
||
color: var(--color-text-secondary);
|
||
font-family: var(--font-label);
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
box-shadow: inset 0 -1px 0 rgba(var(--cc-t1-rgb), 0.08);
|
||
}
|
||
|
||
.settings-zoom-control {
|
||
--settings-zoom-range-progress: 33.333%;
|
||
--settings-zoom-preview-scale: 0.88;
|
||
--settings-zoom-preview-opacity: 0;
|
||
--settings-zoom-track-empty: var(--color-surface-container-high);
|
||
--settings-zoom-thumb-bg: var(--color-surface-container-lowest);
|
||
--settings-zoom-thumb-border: var(--color-border);
|
||
--settings-zoom-thumb-shadow: 0 2px 8px rgba(var(--cc-t1-rgb), 0.16);
|
||
padding-top: 22px;
|
||
}
|
||
|
||
[data-theme="dark"] .settings-zoom-control,
|
||
[data-theme="ink-blue"] .settings-zoom-control {
|
||
--settings-zoom-track-empty: var(--color-surface-container-highest);
|
||
--settings-zoom-thumb-bg: var(--color-surface-bright);
|
||
--settings-zoom-thumb-border: var(--color-brand);
|
||
--settings-zoom-thumb-shadow: 0 0 0 3px rgba(var(--cc-ac-rgb), 0.2), 0 10px 24px rgba(0, 0, 0, 0.5);
|
||
}
|
||
|
||
.settings-zoom-control.is-dragging {
|
||
--settings-zoom-preview-scale: 1;
|
||
--settings-zoom-preview-opacity: 1;
|
||
}
|
||
|
||
.settings-zoom-range-wrap {
|
||
position: relative;
|
||
}
|
||
|
||
.settings-zoom-preview {
|
||
position: absolute;
|
||
left: clamp(24px, var(--settings-zoom-range-progress), calc(100% - 24px));
|
||
top: -30px;
|
||
z-index: 1;
|
||
min-width: 52px;
|
||
transform: translateX(-50%) scale(var(--settings-zoom-preview-scale));
|
||
transform-origin: 50% 100%;
|
||
border: 1px solid var(--color-surface-glass-border);
|
||
border-radius: 999px;
|
||
background: var(--color-surface-glass);
|
||
padding: 4px 9px;
|
||
color: var(--color-text-primary);
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
line-height: 16px;
|
||
text-align: center;
|
||
opacity: var(--settings-zoom-preview-opacity);
|
||
box-shadow: var(--shadow-dropdown);
|
||
backdrop-filter: blur(14px) saturate(1.15);
|
||
-webkit-backdrop-filter: blur(14px) saturate(1.15);
|
||
pointer-events: none;
|
||
transition:
|
||
left 260ms cubic-bezier(0.22, 1, 0.36, 1),
|
||
opacity 160ms ease,
|
||
transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||
}
|
||
|
||
.settings-zoom-preview::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: -4px;
|
||
width: 8px;
|
||
height: 8px;
|
||
transform: translateX(-50%) rotate(45deg);
|
||
border-right: 1px solid var(--color-surface-glass-border);
|
||
border-bottom: 1px solid var(--color-surface-glass-border);
|
||
background: var(--color-surface-glass);
|
||
}
|
||
|
||
.settings-zoom-range {
|
||
appearance: none;
|
||
display: block;
|
||
height: 20px;
|
||
cursor: pointer;
|
||
background: transparent;
|
||
accent-color: var(--color-brand);
|
||
touch-action: none;
|
||
}
|
||
|
||
.settings-zoom-range:focus {
|
||
outline: none;
|
||
}
|
||
|
||
.settings-zoom-range:focus-visible {
|
||
outline: none;
|
||
}
|
||
|
||
.settings-zoom-range::-webkit-slider-runnable-track {
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--color-border);
|
||
background:
|
||
linear-gradient(
|
||
90deg,
|
||
var(--color-brand) 0%,
|
||
var(--color-brand) var(--settings-zoom-range-progress),
|
||
var(--settings-zoom-track-empty) var(--settings-zoom-range-progress),
|
||
var(--settings-zoom-track-empty) 100%
|
||
);
|
||
transition: background 220ms ease, border-color 180ms ease;
|
||
}
|
||
|
||
.settings-zoom-range::-webkit-slider-thumb {
|
||
appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
margin-top: -7px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--settings-zoom-thumb-border);
|
||
background: var(--settings-zoom-thumb-bg);
|
||
box-shadow: var(--settings-zoom-thumb-shadow);
|
||
transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||
}
|
||
|
||
.settings-zoom-range:hover::-webkit-slider-thumb {
|
||
border-color: var(--color-border-focus);
|
||
}
|
||
|
||
.settings-zoom-control.is-dragging .settings-zoom-range::-webkit-slider-thumb {
|
||
transform: scale(1.18);
|
||
border-color: var(--color-brand);
|
||
box-shadow: var(--shadow-dropdown);
|
||
}
|
||
|
||
.settings-zoom-range:focus-visible::-webkit-slider-thumb {
|
||
border-color: var(--color-brand);
|
||
box-shadow: var(--shadow-focus-ring), 0 2px 8px rgba(var(--cc-t1-rgb), 0.16);
|
||
}
|
||
|
||
.settings-zoom-range::-moz-range-track {
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--color-border);
|
||
background: var(--settings-zoom-track-empty);
|
||
transition: background 220ms ease, border-color 180ms ease;
|
||
}
|
||
|
||
.settings-zoom-range::-moz-range-progress {
|
||
height: 6px;
|
||
border-radius: 999px;
|
||
background: var(--color-brand);
|
||
}
|
||
|
||
.settings-zoom-range::-moz-range-thumb {
|
||
width: 18px;
|
||
height: 18px;
|
||
border-radius: 999px;
|
||
border: 1px solid var(--settings-zoom-thumb-border);
|
||
background: var(--settings-zoom-thumb-bg);
|
||
box-shadow: var(--settings-zoom-thumb-shadow);
|
||
transition: border-color 180ms ease, box-shadow 220ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
|
||
}
|
||
|
||
.settings-zoom-control.is-dragging .settings-zoom-range::-moz-range-thumb {
|
||
transform: scale(1.18);
|
||
border-color: var(--color-brand);
|
||
box-shadow: var(--shadow-dropdown);
|
||
}
|
||
|
||
.settings-zoom-range:focus-visible::-moz-range-thumb {
|
||
border-color: var(--color-brand);
|
||
box-shadow: var(--shadow-focus-ring), 0 2px 8px rgba(var(--cc-t1-rgb), 0.16);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.settings-zoom-preview,
|
||
.settings-zoom-range::-webkit-slider-runnable-track,
|
||
.settings-zoom-range::-webkit-slider-thumb,
|
||
.settings-zoom-range::-moz-range-track,
|
||
.settings-zoom-range::-moz-range-thumb {
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
}
|
||
|
||
/* ─── Terminal ANSI palette ───────────────────────────────────────
|
||
The embedded terminal stays dark under all three themes (that is what
|
||
the per-theme `--color-terminal-*` values encode — light/white/dark
|
||
differ only in hue, not in lightness), so the 16 ANSI slots are shared.
|
||
Split them into the theme blocks if a theme ever needs its own ramp.
|
||
Consumed by `lib/terminalTheme.ts`, which is the only place allowed to
|
||
turn these into an xterm ITheme object. */
|
||
:root {
|
||
--color-terminal-ansi-black: #1F1F1F;
|
||
--color-terminal-ansi-red: #FF6D67;
|
||
--color-terminal-ansi-green: #7EF18A;
|
||
--color-terminal-ansi-yellow: #F8C55F;
|
||
--color-terminal-ansi-blue: #77A8FF;
|
||
--color-terminal-ansi-magenta: #D699FF;
|
||
--color-terminal-ansi-cyan: #61D6D6;
|
||
--color-terminal-ansi-white: #D7D2D0;
|
||
--color-terminal-ansi-bright-black: #8F8683;
|
||
--color-terminal-ansi-bright-red: #FF8A85;
|
||
--color-terminal-ansi-bright-green: #9FF7A7;
|
||
--color-terminal-ansi-bright-yellow: #FFDD7A;
|
||
--color-terminal-ansi-bright-blue: #A6C5FF;
|
||
--color-terminal-ansi-bright-magenta: #E3B8FF;
|
||
--color-terminal-ansi-bright-cyan: #8CEEEE;
|
||
--color-terminal-ansi-bright-white: #FFFFFF;
|
||
}
|
||
|
||
/* ─── Layering scale ──────────────────────────────────────────────
|
||
One ordered stack for every overlay. Theme-independent, so it lives
|
||
outside the theme blocks.
|
||
|
||
Two orderings here are deliberate and load-bearing:
|
||
|
||
1. dropdown/popover sit ABOVE dialog. A modal dialog blocks the page
|
||
behind it, so any open dropdown necessarily belongs to the topmost
|
||
dialog. Putting dropdowns below dialogs makes DirectoryPicker
|
||
unusable inside AgentManager's modal — which is exactly why it used
|
||
to hardcode `zIndex: 9999`.
|
||
2. toast sits above sheet. Otherwise a confirmation shown while a
|
||
mobile bottom sheet is open is invisible to the user.
|
||
|
||
Tailwind v4 has no z-index theme namespace, so reference these as
|
||
`z-[var(--z-dialog)]` (or `zIndex: 'var(--z-dialog)'` inline).
|
||
Never introduce a raw z-index outside this scale. */
|
||
:root {
|
||
--z-base: 0;
|
||
--z-raised: 10;
|
||
--z-sticky: 20;
|
||
--z-nav: 30;
|
||
--z-scrim: 40;
|
||
--z-drawer: 50;
|
||
--z-dialog: 60;
|
||
--z-sheet: 65;
|
||
--z-dropdown: 70;
|
||
--z-popover: 75;
|
||
--z-tooltip: 80;
|
||
--z-toast: 90;
|
||
}
|
||
|
||
/* ─── Tailwind static defaults ────────────────────────────────────
|
||
Tailwind v4 resolves `@theme` at build time, so these have to be literal
|
||
values rather than `var()` references. They carry the pure-white palette;
|
||
the `:root` semantic layer below re-points every one of them at a
|
||
`--cc-*` source variable, which is what actually varies per theme. */
|
||
@theme {
|
||
--color-primary: #96442B;
|
||
--color-primary-container: #7C3620;
|
||
--color-primary-fixed: #F6ECE5;
|
||
--color-primary-fixed-dim: #E6CDBF;
|
||
|
||
--color-on-primary: #FBF6EC;
|
||
--color-on-primary-container: #FBF6EC;
|
||
--color-on-surface: #1F1C17;
|
||
--color-on-surface-variant: #5D5850;
|
||
|
||
--color-surface: #FFFFFF;
|
||
--color-surface-bright: #FFFFFF;
|
||
--color-surface-dim: #F0EFE9;
|
||
--color-surface-container: #FAFAF8;
|
||
--color-surface-container-low: #F6F6F3;
|
||
--color-surface-container-high: #F0EFE9;
|
||
--color-surface-container-highest: #EBE9E2;
|
||
--color-surface-container-lowest: #FFFFFF;
|
||
--color-surface-variant: #F6F6F3;
|
||
|
||
--color-background: #FFFFFF;
|
||
|
||
--color-outline: #D8D5CC;
|
||
--color-outline-variant: #EBE9E2;
|
||
|
||
--color-secondary: #96442B;
|
||
--color-secondary-container: #F5ECEA;
|
||
--color-tertiary: #3B7068;
|
||
--color-tertiary-container: #F1F5F4;
|
||
|
||
--color-error: #C74436;
|
||
--color-on-error: #FBF6EC;
|
||
--color-error-container: #FAECE8;
|
||
--color-on-error-container: #BD4133;
|
||
|
||
--color-inverse-surface: #1F1C17;
|
||
--color-inverse-on-surface: #FFFFFF;
|
||
--color-inverse-primary: #E6CDBF;
|
||
|
||
--color-success: #3E8153;
|
||
--color-success-container: #E8F2E9;
|
||
--color-on-success-container: #3A784D;
|
||
--color-info: #96442B;
|
||
--color-info-container: #F5ECEA;
|
||
--color-on-info-container: #96442B;
|
||
--color-warning: #A8751F;
|
||
--color-warning-container: #F8F0DC;
|
||
--color-on-warning-container: #90641B;
|
||
|
||
/* Type roles. Headings are serif — that is the "墨" half of 「纸 · 墨 · 印」.
|
||
Only the latin cut of Noto Serif SC is self-hosted; CJK falls through to
|
||
the platform serif, which is what the handoff prototype does too. */
|
||
--font-headline: 'Noto Serif SC', 'Songti SC', 'Source Han Serif SC', 'Noto Serif CJK SC', 'SimSun', Georgia, serif;
|
||
--font-serif: var(--font-headline);
|
||
--font-body: 'Inter', -apple-system, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
--font-label: var(--font-body);
|
||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||
|
||
/* Corner scale, from the handoff: 6–7 badges / 9–11 controls /
|
||
12–14 inner cards / 16–18 cards / 20 composer / 24 modals / 99 pills. */
|
||
--radius-sm: 6px;
|
||
--radius-md: 10px;
|
||
--radius-lg: 13px;
|
||
--radius-xl: 17px;
|
||
--radius-2xl: 20px;
|
||
--radius-3xl: 24px;
|
||
--radius-full: 9999px;
|
||
}
|
||
|
||
/* ─── Theme sources ───────────────────────────────────────────────
|
||
Six palettes from the 「纸 · 墨 · 印」 handoff. Each block carries only
|
||
raw color values; the semantic layer below turns them into the
|
||
`--color-*` tokens the components consume. Generated values
|
||
(`*-ink`, the heat ramp, the `*-rgb` triplets) are derived from these
|
||
sources — see the header comment on the semantic layer. */
|
||
|
||
/* 纯白 pure-white — 默认 */
|
||
:root,
|
||
[data-theme="white"] {
|
||
color-scheme: light;
|
||
|
||
--cc-bg: #FFFFFF;
|
||
--cc-s0: #F6F6F3;
|
||
--cc-s1: #F0EFE9;
|
||
--cc-s2: #FAFAF8;
|
||
--cc-bd: #EBE9E2;
|
||
--cc-bd2: #D8D5CC;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #918E87;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. */
|
||
--cc-tab-edge: #D9D5CC;
|
||
--cc-tab-sep: #E3DFD6;
|
||
--cc-t1: #1F1C17;
|
||
--cc-t2: #5D5850;
|
||
--cc-t3: #777269;
|
||
|
||
--cc-ac: #96442B;
|
||
--cc-ac2: #7C3620;
|
||
--cc-ac-soft: #F6ECE5;
|
||
--cc-ac-soft-hover: #EFDED4;
|
||
--cc-ac-border: #E6CDBF;
|
||
--cc-ac-ink: #96442B;
|
||
--cc-on-ac: #FBF6EC;
|
||
|
||
--cc-ok: #3E8153;
|
||
--cc-ok-soft: #E8F2E9;
|
||
--cc-ok-ink: #3A784D;
|
||
--cc-wn: #A8751F;
|
||
--cc-wn-soft: #F8F0DC;
|
||
--cc-wn-ink: #90641B;
|
||
--cc-er: #C74436;
|
||
--cc-er-soft: #FAECE8;
|
||
--cc-er-soft-hover: #F5DBD6;
|
||
--cc-er-ink: #BD4133;
|
||
--cc-info: #96442B;
|
||
--cc-info-soft: #F5ECEA;
|
||
--cc-info-ink: #96442B;
|
||
|
||
--cc-teal: #3B7068;
|
||
--cc-teal-soft: #F1F5F4;
|
||
--cc-teal-border: #C0D1CF;
|
||
--cc-teal-icon: #EBF1F0;
|
||
|
||
--cc-diff-add: #E8F3E9;
|
||
--cc-diff-add-gutter: #D9EBDC;
|
||
--cc-diff-add-word: #DEEDE0;
|
||
--cc-diff-del: #FAEEED;
|
||
--cc-diff-del-gutter: #F6E1DF;
|
||
--cc-diff-del-word: #F0CECB;
|
||
--cc-diff-mark: #F4EDE2;
|
||
--cc-diff-mark-gutter: #ECE1CE;
|
||
--cc-code: #F7F7F4;
|
||
|
||
--cc-heat-0: #F0EFE9;
|
||
--cc-heat-1: #E8D6D0;
|
||
--cc-heat-2: #D1ADA2;
|
||
--cc-heat-3: #B8806F;
|
||
--cc-heat-4: #9C4F38;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(30, 28, 22, 0.04), 0 6px 24px rgba(30, 28, 22, 0.06);
|
||
--cc-shadow-composer: 0 1px 2px rgba(30, 28, 22, 0.05), 0 14px 44px rgba(30, 28, 22, 0.08);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(30, 28, 22, 0.10), 0 24px 64px rgba(30, 28, 22, 0.15);
|
||
|
||
--cc-bg-rgb: 255, 255, 255;
|
||
--cc-s1-rgb: 240, 239, 233;
|
||
--cc-t1-rgb: 31, 28, 23;
|
||
--cc-t2-rgb: 93, 88, 80;
|
||
--cc-bd2-rgb: 216, 213, 204;
|
||
--cc-ac-rgb: 150, 68, 43;
|
||
--cc-er-rgb: 199, 68, 54;
|
||
--cc-scrim-rgb: 19, 17, 14;
|
||
}
|
||
|
||
/* 纸墨 paper */
|
||
[data-theme="paper"] {
|
||
color-scheme: light;
|
||
|
||
--cc-bg: #FBF9F4;
|
||
--cc-s0: #F3EFE5;
|
||
--cc-s1: #ECE6D8;
|
||
--cc-s2: #F7F4EC;
|
||
--cc-bd: #E7E0CF;
|
||
--cc-bd2: #D5C9B0;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #938A76;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. */
|
||
--cc-tab-edge: #DBCFB5;
|
||
--cc-tab-sep: #E5D9BE;
|
||
--cc-t1: #231D12;
|
||
--cc-t2: #6E6350;
|
||
--cc-t3: #796E5B;
|
||
|
||
--cc-ac: #96442B;
|
||
--cc-ac2: #7C3620;
|
||
--cc-ac-soft: #F3E6DA;
|
||
--cc-ac-soft-hover: #EBD8C8;
|
||
--cc-ac-border: #E2C7B3;
|
||
--cc-ac-ink: #96442B;
|
||
--cc-on-ac: #FBF6EC;
|
||
|
||
--cc-ok: #477C52;
|
||
--cc-ok-soft: #E8EFE1;
|
||
--cc-ok-ink: #43754D;
|
||
--cc-wn: #A8751F;
|
||
--cc-wn-soft: #F6EED9;
|
||
--cc-wn-ink: #8E631A;
|
||
--cc-er: #C24532;
|
||
--cc-er-soft: #F7E7DF;
|
||
--cc-er-soft-hover: #F2D7CE;
|
||
--cc-er-ink: #B6412F;
|
||
--cc-info: #96442B;
|
||
--cc-info-soft: #F1E7E0;
|
||
--cc-info-ink: #96442B;
|
||
|
||
--cc-teal: #3B7068;
|
||
--cc-teal-soft: #EEEFEA;
|
||
--cc-teal-border: #BECDC7;
|
||
--cc-teal-icon: #E8EBE6;
|
||
|
||
--cc-diff-add: #E9EFDC;
|
||
--cc-diff-add-gutter: #DCE7C9;
|
||
--cc-diff-add-word: #E0E9CF;
|
||
--cc-diff-del: #F6E9E3;
|
||
--cc-diff-del-gutter: #F2DCD5;
|
||
--cc-diff-del-word: #ECCAC2;
|
||
--cc-diff-mark: #F0E8D8;
|
||
--cc-diff-mark-gutter: #E9DCC5;
|
||
--cc-code: #F5F1E4;
|
||
|
||
--cc-heat-0: #ECE6D8;
|
||
--cc-heat-1: #E5D1C8;
|
||
--cc-heat-2: #CFA99C;
|
||
--cc-heat-3: #B67E6B;
|
||
--cc-heat-4: #9C4F37;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(64, 49, 26, 0.05), 0 6px 24px rgba(64, 49, 26, 0.07);
|
||
--cc-shadow-composer: 0 1px 2px rgba(64, 49, 26, 0.06), 0 14px 44px rgba(64, 49, 26, 0.10);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(64, 49, 26, 0.12), 0 24px 64px rgba(64, 49, 26, 0.16);
|
||
|
||
--cc-bg-rgb: 251, 249, 244;
|
||
--cc-s1-rgb: 236, 230, 216;
|
||
--cc-t1-rgb: 35, 29, 18;
|
||
--cc-t2-rgb: 110, 99, 80;
|
||
--cc-bd2-rgb: 213, 201, 176;
|
||
--cc-ac-rgb: 150, 68, 43;
|
||
--cc-er-rgb: 194, 69, 50;
|
||
--cc-scrim-rgb: 21, 17, 11;
|
||
}
|
||
|
||
/* 经典暖色 warm-classic */
|
||
[data-theme="warm-classic"] {
|
||
color-scheme: light;
|
||
|
||
--cc-bg: #F6F0E1;
|
||
--cc-s0: #EDE5D1;
|
||
--cc-s1: #E5DBC3;
|
||
--cc-s2: #F1EBDA;
|
||
--cc-bd: #DED2B8;
|
||
--cc-bd2: #C8B897;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #908369;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. */
|
||
--cc-tab-edge: #D6C5A2;
|
||
--cc-tab-sep: #E2CFAA;
|
||
--cc-t1: #251E11;
|
||
--cc-t2: #6E6146;
|
||
--cc-t3: #746951;
|
||
|
||
--cc-ac: #96442B;
|
||
--cc-ac2: #7C3620;
|
||
--cc-ac-soft: #F0E1D0;
|
||
--cc-ac-soft-hover: #E8D2BE;
|
||
--cc-ac-border: #DFC0A8;
|
||
--cc-ac-ink: #96442B;
|
||
--cc-on-ac: #FBF6EC;
|
||
|
||
--cc-ok: #3E8153;
|
||
--cc-ok-soft: #E8F2E9;
|
||
--cc-ok-ink: #3A784D;
|
||
--cc-wn: #A8751F;
|
||
--cc-wn-soft: #F8F0DC;
|
||
--cc-wn-ink: #90641B;
|
||
--cc-er: #C74436;
|
||
--cc-er-soft: #FAECE8;
|
||
--cc-er-soft-hover: #F5DBD6;
|
||
--cc-er-ink: #BD4133;
|
||
--cc-info: #96442B;
|
||
--cc-info-soft: #ECDFCF;
|
||
--cc-info-ink: #96442B;
|
||
|
||
--cc-teal: #3E6B58;
|
||
--cc-teal-soft: #E9E7D7;
|
||
--cc-teal-border: #BBC5B5;
|
||
--cc-teal-icon: #E4E3D3;
|
||
|
||
--cc-diff-add: #E6ECD3;
|
||
--cc-diff-add-gutter: #D8E2BF;
|
||
--cc-diff-add-word: #DCE5C5;
|
||
--cc-diff-del: #F2E1D2;
|
||
--cc-diff-del-gutter: #EED4C6;
|
||
--cc-diff-del-word: #EAC3B5;
|
||
--cc-diff-mark: #ECE0C8;
|
||
--cc-diff-mark-gutter: #E5D5B6;
|
||
--cc-code: #EFE8D3;
|
||
|
||
--cc-heat-0: #E5DBC3;
|
||
--cc-heat-1: #E1CAB9;
|
||
--cc-heat-2: #CCA491;
|
||
--cc-heat-3: #B57B65;
|
||
--cc-heat-4: #9C4E36;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(64, 49, 26, 0.05), 0 6px 24px rgba(64, 49, 26, 0.07);
|
||
--cc-shadow-composer: 0 1px 2px rgba(64, 49, 26, 0.06), 0 14px 44px rgba(64, 49, 26, 0.10);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(64, 49, 26, 0.12), 0 24px 64px rgba(64, 49, 26, 0.16);
|
||
|
||
--cc-bg-rgb: 246, 240, 225;
|
||
--cc-s1-rgb: 229, 219, 195;
|
||
--cc-t1-rgb: 37, 30, 17;
|
||
--cc-t2-rgb: 110, 97, 70;
|
||
--cc-bd2-rgb: 200, 184, 151;
|
||
--cc-ac-rgb: 150, 68, 43;
|
||
--cc-er-rgb: 199, 68, 54;
|
||
--cc-scrim-rgb: 22, 18, 10;
|
||
}
|
||
|
||
/* 青瓷 celadon */
|
||
[data-theme="celadon"] {
|
||
color-scheme: light;
|
||
|
||
--cc-bg: #F5F8F5;
|
||
--cc-s0: #EAF0EB;
|
||
--cc-s1: #E0E9E2;
|
||
--cc-s2: #F0F4F0;
|
||
--cc-bd: #DCE5DE;
|
||
--cc-bd2: #C6D4C9;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #818D84;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. */
|
||
--cc-tab-edge: #C5D3C8;
|
||
--cc-tab-sep: #CEDCD1;
|
||
--cc-t1: #1C231D;
|
||
--cc-t2: #5D6B60;
|
||
--cc-t3: #667268;
|
||
|
||
--cc-ac: #96442B;
|
||
--cc-ac2: #7C3620;
|
||
--cc-ac-soft: #F0E7DC;
|
||
--cc-ac-soft-hover: #E8DACB;
|
||
--cc-ac-border: #DFC9B6;
|
||
--cc-ac-ink: #96442B;
|
||
--cc-on-ac: #FBF6EC;
|
||
|
||
--cc-ok: #3E8153;
|
||
--cc-ok-soft: #E3EFE5;
|
||
--cc-ok-ink: #39764C;
|
||
--cc-wn: #A8751F;
|
||
--cc-wn-soft: #F8F0DC;
|
||
--cc-wn-ink: #90641B;
|
||
--cc-er: #C74436;
|
||
--cc-er-soft: #FAECE8;
|
||
--cc-er-soft-hover: #F5DBD6;
|
||
--cc-er-ink: #BD4133;
|
||
--cc-info: #96442B;
|
||
--cc-info-soft: #ECE6E1;
|
||
--cc-info-ink: #96442B;
|
||
|
||
--cc-teal: #2F6E63;
|
||
--cc-teal-soft: #E7EEEB;
|
||
--cc-teal-border: #B6CCC6;
|
||
--cc-teal-icon: #E1EAE6;
|
||
|
||
--cc-diff-add: #E4F0E5;
|
||
--cc-diff-add-gutter: #D4E6D7;
|
||
--cc-diff-add-word: #D9E9DB;
|
||
--cc-diff-del: #F1E8E4;
|
||
--cc-diff-del-gutter: #EEDBD6;
|
||
--cc-diff-del-word: #E9C9C3;
|
||
--cc-diff-mark: #EBE7D9;
|
||
--cc-diff-mark-gutter: #E4DBC6;
|
||
--cc-code: #EFF4F0;
|
||
|
||
--cc-heat-0: #E0E9E2;
|
||
--cc-heat-1: #E0D0C9;
|
||
--cc-heat-2: #CBA99C;
|
||
--cc-heat-3: #B47E6C;
|
||
--cc-heat-4: #9C4F37;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(28, 42, 32, 0.05), 0 6px 24px rgba(28, 42, 32, 0.06);
|
||
--cc-shadow-composer: 0 1px 2px rgba(28, 42, 32, 0.05), 0 14px 44px rgba(28, 42, 32, 0.08);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(28, 42, 32, 0.10), 0 24px 64px rgba(28, 42, 32, 0.15);
|
||
|
||
--cc-bg-rgb: 245, 248, 245;
|
||
--cc-s1-rgb: 224, 233, 226;
|
||
--cc-t1-rgb: 28, 35, 29;
|
||
--cc-t2-rgb: 93, 107, 96;
|
||
--cc-bd2-rgb: 198, 212, 201;
|
||
--cc-ac-rgb: 150, 68, 43;
|
||
--cc-er-rgb: 199, 68, 54;
|
||
--cc-scrim-rgb: 17, 21, 17;
|
||
}
|
||
|
||
/* 墨夜 dark */
|
||
[data-theme="dark"] {
|
||
color-scheme: dark;
|
||
|
||
--cc-bg: #201D17;
|
||
--cc-s0: #1A1813;
|
||
--cc-s1: #2B271F;
|
||
--cc-s2: #252119;
|
||
--cc-bd: #383226;
|
||
--cc-bd2: #4C4433;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #756D5C;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. Both go
|
||
*lighter* than the trough here: an ink theme's paper is already so dark
|
||
that pure black is only 1.26:1 against it, so there is no room downward. */
|
||
--cc-tab-edge: #353024;
|
||
--cc-tab-sep: #2D281F;
|
||
--cc-t1: #EDE6D6;
|
||
--cc-t2: #A79B83;
|
||
--cc-t3: #918777;
|
||
|
||
--cc-ac: #D07B52;
|
||
--cc-ac2: #DE8F66;
|
||
--cc-ac-soft: #3B2A1E;
|
||
--cc-ac-soft-hover: #493424;
|
||
--cc-ac-border: #59402C;
|
||
--cc-ac-ink: #D2815B;
|
||
--cc-on-ac: #201D17;
|
||
|
||
--cc-ok: #84B383;
|
||
--cc-ok-soft: #28301D;
|
||
--cc-ok-ink: #84B383;
|
||
--cc-wn: #D3A34C;
|
||
--cc-wn-soft: #332916;
|
||
--cc-wn-ink: #D3A34C;
|
||
--cc-er: #E06B52;
|
||
--cc-er-soft: #3A241B;
|
||
--cc-er-soft-hover: #552F24;
|
||
--cc-er-ink: #E17058;
|
||
--cc-info: #D07B52;
|
||
--cc-info-soft: #3C2C20;
|
||
--cc-info-ink: #D3845E;
|
||
|
||
--cc-teal: #7DD3C7;
|
||
--cc-teal-soft: #2B332C;
|
||
--cc-teal-border: #3C544C;
|
||
--cc-teal-icon: #313E37;
|
||
|
||
--cc-diff-add: #2B331E;
|
||
--cc-diff-add-gutter: #354026;
|
||
--cc-diff-add-word: #323C24;
|
||
--cc-diff-del: #3B281F;
|
||
--cc-diff-del-gutter: #462D23;
|
||
--cc-diff-del-word: #563328;
|
||
--cc-diff-mark: #39301E;
|
||
--cc-diff-mark-gutter: #443822;
|
||
--cc-code: #1A1813;
|
||
|
||
--cc-heat-0: #2B271F;
|
||
--cc-heat-1: #473224;
|
||
--cc-heat-2: #6D4631;
|
||
--cc-heat-3: #985D3F;
|
||
--cc-heat-4: #C5754E;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.35), 0 6px 24px rgba(0, 0, 0, 0.35);
|
||
--cc-shadow-composer: 0 1px 2px rgba(0, 0, 0, 0.45), 0 14px 44px rgba(0, 0, 0, 0.50);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(0, 0, 0, 0.50), 0 24px 64px rgba(0, 0, 0, 0.55);
|
||
|
||
--cc-bg-rgb: 32, 29, 23;
|
||
--cc-s1-rgb: 43, 39, 31;
|
||
--cc-t1-rgb: 237, 230, 214;
|
||
--cc-t2-rgb: 167, 155, 131;
|
||
--cc-bd2-rgb: 76, 68, 51;
|
||
--cc-ac-rgb: 208, 123, 82;
|
||
--cc-er-rgb: 224, 107, 82;
|
||
--cc-scrim-rgb: 6, 6, 6;
|
||
}
|
||
|
||
/* 墨夜蓝 ink-blue */
|
||
[data-theme="ink-blue"] {
|
||
color-scheme: dark;
|
||
|
||
--cc-bg: #1A1D24;
|
||
--cc-s0: #15181E;
|
||
--cc-s1: #252A35;
|
||
--cc-s2: #20242D;
|
||
--cc-bd: #2F3441;
|
||
--cc-bd2: #434A5B;
|
||
/* Control boundary — clears WCAG 1.4.11's 3:1 on the card lining. */
|
||
--cc-bd-strong: #6A707E;
|
||
/* Tab strip. Trough stays --cc-s0, so these two carry the shape. Both go
|
||
*lighter* than the trough here: an ink theme's paper is already so dark
|
||
that pure black is only 1.25:1 against it, so there is no room downward. */
|
||
--cc-tab-edge: #2C303C;
|
||
--cc-tab-sep: #252833;
|
||
--cc-t1: #E8EAF0;
|
||
--cc-t2: #9AA1B2;
|
||
--cc-t3: #858B97;
|
||
|
||
--cc-ac: #D07B52;
|
||
--cc-ac2: #DE8F66;
|
||
--cc-ac-soft: #37281F;
|
||
--cc-ac-soft-hover: #453326;
|
||
--cc-ac-border: #57402F;
|
||
--cc-ac-ink: #D17E56;
|
||
--cc-on-ac: #1A1D24;
|
||
|
||
--cc-ok: #6FB58C;
|
||
--cc-ok-soft: #213026;
|
||
--cc-ok-ink: #6FB58C;
|
||
--cc-wn: #D3A34C;
|
||
--cc-wn-soft: #2F2917;
|
||
--cc-wn-ink: #D3A34C;
|
||
--cc-er: #E0685E;
|
||
--cc-er-soft: #37211D;
|
||
--cc-er-soft-hover: #522C27;
|
||
--cc-er-ink: #E06A60;
|
||
--cc-info: #8FA8E8;
|
||
--cc-info-soft: #2D3343;
|
||
--cc-info-ink: #8FA8E8;
|
||
|
||
--cc-teal: #7DD3C7;
|
||
--cc-teal-soft: #263338;
|
||
--cc-teal-border: #385455;
|
||
--cc-teal-icon: #2C3E41;
|
||
|
||
--cc-diff-add: #233028;
|
||
--cc-diff-add-gutter: #2C3B31;
|
||
--cc-diff-add-word: #29382E;
|
||
--cc-diff-del: #36282C;
|
||
--cc-diff-del-gutter: #422C30;
|
||
--cc-diff-del-word: #513234;
|
||
--cc-diff-mark: #34302A;
|
||
--cc-diff-mark-gutter: #3F382C;
|
||
--cc-code: #15181E;
|
||
|
||
--cc-heat-0: #252A35;
|
||
--cc-heat-1: #42322E;
|
||
--cc-heat-2: #6A4638;
|
||
--cc-heat-3: #965D43;
|
||
--cc-heat-4: #C5754F;
|
||
|
||
--cc-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.35), 0 6px 24px rgba(0, 0, 0, 0.35);
|
||
--cc-shadow-composer: 0 1px 2px rgba(0, 0, 0, 0.45), 0 14px 44px rgba(0, 0, 0, 0.50);
|
||
--cc-shadow-overlay: 0 8px 24px rgba(0, 0, 0, 0.50), 0 24px 64px rgba(0, 0, 0, 0.55);
|
||
|
||
--cc-bg-rgb: 26, 29, 36;
|
||
--cc-s1-rgb: 37, 42, 53;
|
||
--cc-t1-rgb: 232, 234, 240;
|
||
--cc-t2-rgb: 154, 161, 178;
|
||
--cc-bd2-rgb: 67, 74, 91;
|
||
--cc-ac-rgb: 208, 123, 82;
|
||
--cc-er-rgb: 224, 104, 94;
|
||
--cc-scrim-rgb: 6, 6, 6;
|
||
}
|
||
|
||
/* ─── Semantic layer ──────────────────────────────────────────────
|
||
Every `--color-*` the app consumes is defined exactly once, here, in terms
|
||
of the `--cc-*` source variables above. Adding a theme therefore means
|
||
adding one source block — never touching this layer.
|
||
|
||
This replaced three hand-maintained per-theme copies of the same 200 tokens,
|
||
where the failure mode was a token defined in two themes and forgotten in
|
||
the third: it renders transparent, and only in that one theme. */
|
||
:root {
|
||
/* Layout dimensions */
|
||
--sidebar-width: 280px;
|
||
--sidebar-rail-width: 72px;
|
||
--titlebar-height: 52px;
|
||
--statusbar-height: 36px;
|
||
--motion-sidebar-duration: 280ms;
|
||
--motion-sidebar-easing: cubic-bezier(0.22, 1, 0.36, 1);
|
||
|
||
/* Surfaces. The handoff has four grounds: page (--bg), sidebar and
|
||
secondary panels (--s0), hover/selected (--s1), card lining (--s2). */
|
||
--color-background: var(--cc-bg);
|
||
--color-surface: var(--cc-bg);
|
||
--color-surface-bright: var(--cc-bg);
|
||
--color-surface-container-lowest: var(--cc-bg);
|
||
--color-surface-container: var(--cc-s2);
|
||
--color-surface-container-low: var(--cc-s0);
|
||
--color-surface-container-high: var(--cc-s1);
|
||
--color-surface-container-highest: var(--cc-bd);
|
||
--color-surface-variant: var(--cc-s0);
|
||
--color-surface-dim: var(--cc-s1);
|
||
--color-surface-sidebar: var(--cc-s0);
|
||
--color-surface-hover: var(--cc-s1);
|
||
--color-surface-selected: var(--cc-s1);
|
||
--color-surface-info: var(--cc-s0);
|
||
--color-surface-user-msg: var(--cc-s1);
|
||
|
||
/* Borders */
|
||
--color-outline: var(--cc-bd2);
|
||
--color-outline-variant: var(--cc-bd);
|
||
--color-border: var(--cc-bd);
|
||
/* For CONTROL boundaries (checkbox/radio outlines): --color-border is a
|
||
hairline separator at ~1.2:1 and fails WCAG 1.4.11 the moment it has to
|
||
delineate an interactive control. */
|
||
--color-border-strong: var(--cc-bd-strong);
|
||
--color-border-separator: var(--cc-bd);
|
||
--color-border-focus: var(--cc-ac);
|
||
/* Tab strip outlines. The strip's trough deliberately stays the sidebar's
|
||
own ground, so the selected tab's fill is only 1.05–1.10:1 against it in
|
||
all six themes — far too little for a rounded corner to be *visible*, and
|
||
a curve nobody can see is not a curve. These two carry the shape instead:
|
||
`tab-edge` traces the selected tab at ~1.35:1 against the trough,
|
||
`tab-separator` is the hairline between two neighbouring idle tabs at
|
||
~1.22:1. They cannot be folded into `--color-border`/`--color-outline`:
|
||
those are calibrated against paper, and on the trough they land at 1.12
|
||
(invisible) and 1.36–2.01 (a drawn box) respectively, in opposite
|
||
directions per theme family. */
|
||
--color-tab-edge: var(--cc-tab-edge);
|
||
--color-tab-separator: var(--cc-tab-sep);
|
||
|
||
/* Text */
|
||
--color-on-surface: var(--cc-t1);
|
||
--color-on-surface-variant: var(--cc-t2);
|
||
--color-text-primary: var(--cc-t1);
|
||
--color-text-secondary: var(--cc-t2);
|
||
--color-text-tertiary: var(--cc-t3);
|
||
--color-text-accent: var(--cc-info);
|
||
|
||
/* Terracotta seal — the brand accent */
|
||
--color-primary: var(--cc-ac);
|
||
--color-primary-container: var(--cc-ac2);
|
||
--color-primary-fixed: var(--cc-ac-soft);
|
||
--color-primary-fixed-dim: var(--cc-ac-border);
|
||
--color-on-primary: var(--cc-on-ac);
|
||
--color-on-primary-container: var(--cc-on-ac);
|
||
--color-brand: var(--cc-ac);
|
||
--color-brand-hover: var(--cc-ac2);
|
||
/* Opaque on purpose: the `/10` alpha modifier compiles to a color function
|
||
Safari 15 WebViews cannot parse (guarded in vite-config.test.ts). */
|
||
--color-brand-soft: var(--cc-ac-soft);
|
||
--color-brand-soft-hover: var(--cc-ac-soft-hover);
|
||
/* Text sitting ON `--color-brand-soft`. Not the same as `--color-brand`:
|
||
the two ink palettes' terracotta lands at 4.3–4.5:1 on their own soft
|
||
fill, so a terracotta chip label has to use this darkened pair instead. */
|
||
--color-on-brand-soft: var(--cc-ac-ink);
|
||
/* Inner rule engraved on the large seal — the accent's own foreground at
|
||
40%, so it stays a highlight rather than a hard white line. */
|
||
--color-brand-seal-rule: rgba(251, 246, 236, 0.4);
|
||
--color-inverse-surface: var(--cc-t1);
|
||
--color-inverse-on-surface: var(--cc-bg);
|
||
--color-inverse-primary: var(--cc-ac-border);
|
||
|
||
--color-secondary: var(--cc-info);
|
||
--color-secondary-container: var(--cc-info-soft);
|
||
--color-tertiary: var(--cc-teal);
|
||
--color-tertiary-container: var(--cc-teal-soft);
|
||
|
||
/* Status tones. `--color-on-<tone>-container` is the AA-checked foreground
|
||
for `--color-<tone>-container`; `--color-<tone>` alone is too light on its
|
||
own fill (that is the 2.66:1 warning badge contrast.test.ts guards). */
|
||
--color-success: var(--cc-ok);
|
||
--color-success-container: var(--cc-ok-soft);
|
||
--color-on-success-container: var(--cc-ok-ink);
|
||
--color-warning: var(--cc-wn);
|
||
--color-warning-container: var(--cc-wn-soft);
|
||
--color-on-warning-container: var(--cc-wn-ink);
|
||
--color-error: var(--cc-er);
|
||
--color-on-error: var(--cc-on-ac);
|
||
--color-error-container: var(--cc-er-soft);
|
||
--color-on-error-container: var(--cc-er-ink);
|
||
--color-error-soft: var(--cc-er-soft);
|
||
--color-error-soft-hover: var(--cc-er-soft-hover);
|
||
--color-info: var(--cc-info);
|
||
--color-info-container: var(--cc-info-soft);
|
||
--color-on-info-container: var(--cc-info-ink);
|
||
|
||
/* Memory / goal surfaces keep a teal identity of their own */
|
||
--color-memory-accent: var(--cc-teal);
|
||
--color-memory-surface: var(--cc-teal-soft);
|
||
--color-memory-border: var(--cc-teal-border);
|
||
--color-memory-icon-bg: var(--cc-teal-icon);
|
||
--color-goal-accent: var(--cc-teal);
|
||
--color-goal-surface: var(--cc-teal-soft);
|
||
--color-goal-border: var(--cc-teal-border);
|
||
--color-goal-icon-bg: var(--cc-teal-icon);
|
||
--color-goal-chip-bg: var(--cc-bg);
|
||
--color-goal-chip-border: var(--cc-bd);
|
||
|
||
/* Controls */
|
||
--color-model-option-selected-bg: var(--cc-ac-soft);
|
||
--color-model-option-selected-border: var(--cc-ac-border);
|
||
--color-switch-checked-bg: var(--color-brand);
|
||
--color-switch-thumb: var(--cc-bg);
|
||
--color-btn-primary-bg: var(--cc-t1);
|
||
--color-btn-primary-fg: var(--cc-bg);
|
||
/* Flat ink, not a gradient: the handoff's primary action is a solid ink
|
||
block that turns terracotta on hover. Kept as gradient-shaped tokens so
|
||
existing call sites keep resolving. */
|
||
--gradient-btn-primary: linear-gradient(var(--cc-t1), var(--cc-t1));
|
||
--gradient-btn-primary-hover: linear-gradient(var(--cc-ac), var(--cc-ac));
|
||
|
||
/* Sidebar chrome */
|
||
--color-sidebar-filter-bg: var(--cc-bg);
|
||
--color-sidebar-filter-border: var(--cc-bd);
|
||
--color-sidebar-filter-icon-bg: transparent;
|
||
--color-sidebar-search-bg: var(--cc-bg);
|
||
--color-sidebar-search-border: var(--cc-bd);
|
||
--color-sidebar-item-hover: var(--cc-s1);
|
||
--color-sidebar-item-active: var(--cc-bg);
|
||
--color-sidebar-item-active-border: var(--cc-bd);
|
||
--sidebar-panel-bg-image: none;
|
||
|
||
/* Activity heatmap */
|
||
--color-activity-heat-0: var(--cc-heat-0);
|
||
--color-activity-heat-1: var(--cc-heat-1);
|
||
--color-activity-heat-2: var(--cc-heat-2);
|
||
--color-activity-heat-3: var(--cc-heat-3);
|
||
--color-activity-heat-4: var(--cc-heat-4);
|
||
--color-activity-cell-border: rgba(var(--cc-bd2-rgb), 0.42);
|
||
--color-activity-cell-border-hover: rgba(var(--cc-ac-rgb), 0.58);
|
||
--color-activity-cell-border-active: var(--cc-t1);
|
||
--shadow-activity-cell-hover: 0 0 0 2px rgba(var(--cc-ac-rgb), 0.14), 0 8px 18px rgba(var(--cc-scrim-rgb), 0.18);
|
||
--color-activity-tooltip-surface: var(--cc-t1);
|
||
--color-activity-tooltip-border: rgba(var(--cc-bg-rgb), 0.16);
|
||
--color-activity-tooltip-text: var(--cc-bg);
|
||
--color-activity-tooltip-muted: var(--cc-ac-border);
|
||
|
||
/* Trace inspector: the handoff renders it with the ordinary panel palette
|
||
rather than a private one, so these now alias straight through. */
|
||
--color-inspector-surface: var(--cc-bg);
|
||
--color-inspector-panel: var(--cc-s0);
|
||
--color-inspector-chip: var(--cc-s1);
|
||
--color-inspector-border: var(--cc-bd);
|
||
--color-inspector-accent: var(--cc-ac);
|
||
--color-inspector-accent-hover: var(--cc-ac2);
|
||
--color-inspector-accent-secondary: var(--cc-info);
|
||
--color-inspector-text: var(--cc-t1);
|
||
--color-inspector-heading: var(--cc-t1);
|
||
--color-inspector-muted: var(--cc-t3);
|
||
--color-inspector-muted-strong: var(--cc-t2);
|
||
--color-inspector-success: var(--cc-ok-ink);
|
||
--color-inspector-success-bg: var(--cc-ok-soft);
|
||
--color-inspector-danger: var(--cc-er-ink);
|
||
--color-inspector-danger-bg: var(--cc-er-soft);
|
||
--color-inspector-danger-surface: var(--cc-er-soft);
|
||
--color-inspector-danger-border: var(--cc-er-soft-hover);
|
||
--color-inspector-capacity: var(--cc-t3);
|
||
|
||
/* Overlays */
|
||
/* `--color-overlay-scrim` dims for non-modal layers (the mobile sidebar
|
||
drawer); `--color-modal-scrim` is the heavier one true modals sit on. */
|
||
--color-overlay-scrim: rgba(var(--cc-scrim-rgb), 0.42);
|
||
--color-modal-scrim: rgba(var(--cc-scrim-rgb), 0.5);
|
||
/* Dialogs are opaque: see `.dialog-panel`. Light themes put the paper at the
|
||
top of the ramp, so the lifted surface is the background value itself. */
|
||
--color-surface-dialog: var(--cc-bg);
|
||
--color-surface-glass: rgba(var(--cc-bg-rgb), 0.92);
|
||
--color-surface-glass-border: rgba(var(--cc-bd2-rgb), 0.4);
|
||
|
||
/* Shadows: card / composer / floating overlay */
|
||
--shadow-card: var(--cc-shadow-card);
|
||
--shadow-composer: var(--cc-shadow-composer);
|
||
--shadow-overlay: var(--cc-shadow-overlay);
|
||
--shadow-dropdown: var(--cc-shadow-overlay);
|
||
--shadow-inspector: var(--cc-shadow-overlay);
|
||
--shadow-sidebar-filter: none;
|
||
--shadow-focus-ring: 0 0 0 1px rgba(var(--cc-ac-rgb), 0.24);
|
||
--shadow-error-ring: 0 0 0 1px rgba(var(--cc-er-rgb), 0.26);
|
||
--shadow-button-primary: 0 2px 10px rgba(var(--cc-t1-rgb), 0.22);
|
||
|
||
/* Code + diff */
|
||
--color-code-bg: var(--cc-code);
|
||
--color-code-fg: var(--cc-t1);
|
||
--color-diff-added-bg: var(--cc-diff-add);
|
||
--color-diff-added-word: var(--cc-diff-add-word);
|
||
--color-diff-added-gutter: var(--cc-diff-add-gutter);
|
||
--color-diff-added-text: var(--cc-ok-ink);
|
||
--color-diff-removed-bg: var(--cc-diff-del);
|
||
--color-diff-removed-word: var(--cc-diff-del-word);
|
||
--color-diff-removed-gutter: var(--cc-diff-del-gutter);
|
||
--color-diff-removed-text: var(--cc-er-ink);
|
||
--color-diff-highlight-bg: var(--cc-diff-mark);
|
||
--color-diff-highlight-gutter: var(--cc-diff-mark-gutter);
|
||
--color-diff-title-bg: var(--cc-s0);
|
||
--color-diff-title-color: var(--cc-t3);
|
||
--color-diff-title-border: var(--cc-bd);
|
||
|
||
/* Syntax highlighting. One ramp for the four paper themes; the two ink
|
||
themes override it below. */
|
||
--color-code-comment: #8A8478;
|
||
--color-code-string: #437220;
|
||
--color-code-keyword: #B8533B;
|
||
--color-code-function: #1D5A8C;
|
||
--color-code-number: #1B7A6A;
|
||
--color-code-property: #7A3E20;
|
||
--color-code-type: #7E5520;
|
||
--color-code-parameter: #5C3D2E;
|
||
--color-code-punctuation: #5C504A;
|
||
--color-code-inserted: var(--cc-ok-ink);
|
||
--color-code-deleted: var(--cc-er-ink);
|
||
|
||
--color-diff-syntax-foreground: var(--cc-t1);
|
||
--color-diff-syntax-comment: #8A8478;
|
||
--color-diff-syntax-string: #437220;
|
||
--color-diff-syntax-number: #1B7A6A;
|
||
--color-diff-syntax-regexp: #1D5A8C;
|
||
--color-diff-syntax-keyword: #B8533B;
|
||
--color-diff-syntax-variable: #7A3E20;
|
||
--color-diff-syntax-parameter: #5C504A;
|
||
--color-diff-syntax-property: #7A3E20;
|
||
--color-diff-syntax-function: #1D5A8C;
|
||
--color-diff-syntax-type: #7E5520;
|
||
--color-diff-syntax-punctuation: #5C504A;
|
||
|
||
/* Terminal. The handoff pins one warm-ink panel across every theme; the
|
||
ink-blue theme swaps in its own cool ground below. */
|
||
--color-terminal-header: #221F19;
|
||
--color-terminal-bg: #191713;
|
||
--color-terminal-border: #14120F;
|
||
--color-terminal-fg: #E4DCCA;
|
||
--color-terminal-cursor: #D07B52;
|
||
--color-terminal-selection: #4C4433;
|
||
--color-terminal-muted: #8B8069;
|
||
--color-terminal-accent: #84B383;
|
||
--color-terminal-danger: #E06B52;
|
||
--color-terminal-warning: #D3A34C;
|
||
|
||
/* Find-in-page highlights. These paint over arbitrary page content, not
|
||
over a brand-soft panel, so they get their own pair instead of borrowing
|
||
the brand chip tokens: an amber wash for matches (browser convention),
|
||
full-strength terracotta for the active one. */
|
||
--color-search-highlight: var(--cc-diff-mark);
|
||
--color-on-search-highlight: var(--cc-t1);
|
||
--color-search-highlight-active: var(--cc-ac);
|
||
--color-on-search-highlight-active: var(--cc-on-ac);
|
||
|
||
/* Misc */
|
||
--color-window-close-hover: #C74436;
|
||
--color-selection-bg: var(--cc-ac-border);
|
||
--color-selection-fg: var(--cc-t1);
|
||
--color-chat-navigation-target: rgba(var(--cc-ac-rgb), 0.10);
|
||
--color-text-secondary-a72: rgba(var(--cc-t2-rgb), 0.72);
|
||
--color-text-secondary-a68: rgba(var(--cc-t2-rgb), 0.68);
|
||
--color-text-primary-a88: rgba(var(--cc-t1-rgb), 0.88);
|
||
--color-text-primary-a82: rgba(var(--cc-t1-rgb), 0.82);
|
||
--color-text-primary-a78: rgba(var(--cc-t1-rgb), 0.78);
|
||
--color-surface-hover-a34: rgba(var(--cc-s1-rgb), 0.34);
|
||
--color-surface-hover-a54: rgba(var(--cc-s1-rgb), 0.54);
|
||
--color-outline-a72: rgba(var(--cc-bd2-rgb), 0.72);
|
||
--color-outline-a78: rgba(var(--cc-bd2-rgb), 0.78);
|
||
--color-outline-a92: rgba(var(--cc-bd2-rgb), 0.92);
|
||
|
||
/* Radii — mirrored from `@theme` so `var(--radius-*)` resolves at runtime */
|
||
--radius-sm: 6px;
|
||
--radius-md: 10px;
|
||
--radius-lg: 13px;
|
||
--radius-xl: 17px;
|
||
--radius-2xl: 20px;
|
||
--radius-3xl: 24px;
|
||
--radius-full: 9999px;
|
||
}
|
||
|
||
/* Ink themes: lift the syntax ramp off the paper values. */
|
||
[data-theme="dark"],
|
||
[data-theme="ink-blue"] {
|
||
--color-code-comment: #8F8683;
|
||
--color-code-string: #A8CE8C;
|
||
--color-code-keyword: #E9926B;
|
||
--color-code-function: #7FC7E8;
|
||
--color-code-number: #86D0C4;
|
||
--color-code-property: #E8BBA1;
|
||
--color-code-type: #D9BE8A;
|
||
--color-code-parameter: var(--cc-t1);
|
||
--color-code-punctuation: #9B908C;
|
||
|
||
--color-diff-syntax-comment: #8F8683;
|
||
--color-diff-syntax-string: #A8CE8C;
|
||
--color-diff-syntax-number: #86D0C4;
|
||
--color-diff-syntax-regexp: #7FC7E8;
|
||
--color-diff-syntax-keyword: #E9926B;
|
||
--color-diff-syntax-variable: #E8BBA1;
|
||
--color-diff-syntax-parameter: #9B908C;
|
||
--color-diff-syntax-property: #E8BBA1;
|
||
--color-diff-syntax-function: #7FC7E8;
|
||
--color-diff-syntax-type: #D9BE8A;
|
||
--color-diff-syntax-punctuation: #9B908C;
|
||
|
||
--color-selection-fg: var(--cc-t1);
|
||
/* Ink themes run the background at the bottom of the ramp, so a lifted
|
||
surface has to climb it — `--cc-s1`, not the light themes' `--cc-bg`. */
|
||
--color-surface-dialog: var(--cc-s1);
|
||
--color-surface-glass: rgba(var(--cc-bg-rgb), 0.93);
|
||
--color-modal-scrim: rgba(var(--cc-scrim-rgb), 0.66);
|
||
--color-overlay-scrim: rgba(var(--cc-scrim-rgb), 0.56);
|
||
--shadow-button-primary: 0 2px 10px rgba(0, 0, 0, 0.4);
|
||
--color-activity-tooltip-border: rgba(var(--cc-bg-rgb), 0.28);
|
||
}
|
||
|
||
/* Ink-blue runs a cool terminal ground rather than the shared warm ink. */
|
||
[data-theme="ink-blue"] {
|
||
--color-terminal-header: #1B1F27;
|
||
--color-terminal-bg: #14171D;
|
||
--color-terminal-border: #101318;
|
||
--color-terminal-fg: #DDE1EB;
|
||
--color-terminal-selection: #434A5B;
|
||
--color-terminal-muted: #79808F;
|
||
}
|
||
|
||
/* ─── Material Symbols ─────────────────────────────────────────────
|
||
Inside @layer base ON PURPOSE. Unlayered CSS beats every @layer, and
|
||
Tailwind v4 puts its utilities in `@layer utilities` — so an unlayered
|
||
`font-size: 24px` here silently overrode every `text-[14px]` on every
|
||
hand-written icon span in the app, regardless of source order. In base,
|
||
the 24px default still applies when no size utility is present, and the
|
||
utilities win when one is. */
|
||
@layer base {
|
||
.material-symbols-outlined {
|
||
font-family: 'Material Symbols Outlined';
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
font-size: 24px;
|
||
line-height: 1;
|
||
letter-spacing: normal;
|
||
text-transform: none;
|
||
display: inline-block;
|
||
white-space: nowrap;
|
||
word-wrap: normal;
|
||
direction: ltr;
|
||
-webkit-font-feature-settings: 'liga';
|
||
-webkit-font-smoothing: antialiased;
|
||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||
vertical-align: middle;
|
||
}
|
||
}
|
||
|
||
/* ─── Base styles ──────────────────────────────────────────────── */
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html, body, #root {
|
||
height: 100%;
|
||
margin: 0;
|
||
padding: 0;
|
||
font-family: var(--font-body);
|
||
color: var(--color-on-surface);
|
||
background-color: var(--color-background);
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
::selection {
|
||
background: var(--color-selection-bg);
|
||
color: var(--color-selection-fg);
|
||
}
|
||
|
||
/* Desktop drag region */
|
||
[data-desktop-drag-region] {
|
||
app-region: drag;
|
||
-webkit-app-region: drag;
|
||
}
|
||
button, input, textarea, select, a, [role="button"] {
|
||
app-region: no-drag;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
[draggable="true"] {
|
||
app-region: no-drag;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
.tab-bar-interactive,
|
||
.tab-bar-interactive * {
|
||
app-region: no-drag;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
|
||
/* Chrome's hairline between two neighbouring idle tabs — the thing that keeps
|
||
a row of untitled sessions from reading as one continuous smear.
|
||
It belongs to the *gap*, not to either tab, so it has to vanish the moment
|
||
either side of that gap gets a fill: a line running alongside a rounded
|
||
corner reads as a crack in it. Only sibling combinators can express "the
|
||
tab after the hovered one", which is why this is here and not in a Tailwind
|
||
class list. `::after` on purpose — `::before` is taken by the drag-over
|
||
drop indicator. */
|
||
.tab-strip-item::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: -1px;
|
||
top: 50%;
|
||
width: 1px;
|
||
height: 16px;
|
||
transform: translateY(-50%);
|
||
background: var(--color-tab-separator);
|
||
pointer-events: none;
|
||
}
|
||
.tab-strip-item:first-child::after,
|
||
.tab-strip-item:hover::after,
|
||
.tab-strip-item[data-active='true']::after,
|
||
.tab-strip-item[data-dragging='true']::after,
|
||
.tab-strip-item:hover + .tab-strip-item::after,
|
||
.tab-strip-item[data-active='true'] + .tab-strip-item::after,
|
||
.tab-strip-item[data-dragging='true'] + .tab-strip-item::after {
|
||
content: none;
|
||
}
|
||
|
||
.glass-panel {
|
||
background: var(--color-surface-glass);
|
||
border: 1px solid var(--color-surface-glass-border);
|
||
backdrop-filter: blur(18px);
|
||
-webkit-backdrop-filter: blur(18px);
|
||
box-shadow: var(--shadow-overlay);
|
||
}
|
||
|
||
/* The fill above is translucent on the assumption that the blur will scramble
|
||
whatever shows through. Where `backdrop-filter` is unavailable that
|
||
assumption fails silently — no fallback fires, the blur is simply skipped,
|
||
and page text stays legible straight through every dropdown and popover.
|
||
Going opaque costs the glass look only on engines that never rendered it. */
|
||
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
|
||
.glass-panel {
|
||
background: rgb(var(--cc-bg-rgb));
|
||
}
|
||
}
|
||
|
||
.glass-panel:focus-within {
|
||
box-shadow: var(--shadow-focus-ring), var(--shadow-dropdown);
|
||
}
|
||
|
||
/* Modals are read-and-fill surfaces, not glances: an 860px provider form that
|
||
scrolls needs the page behind it gone, not softened. `.glass-panel` couples
|
||
its fill to `backdrop-filter`, and a dialog this large cannot take the bet
|
||
that the blur is live — when it is not, the page reads through the form.
|
||
No `:focus-within` ring: a dialog holds focus essentially always, so the
|
||
glass variant's ring would burn permanently rather than signal anything. */
|
||
.dialog-panel {
|
||
background: var(--color-surface-dialog);
|
||
border: 1px solid var(--color-border);
|
||
box-shadow: var(--shadow-overlay);
|
||
}
|
||
|
||
/* The composer sits closer to the page than a floating overlay does, so it
|
||
takes the middle step of the shadow scale. Declared as a class rather than a
|
||
`shadow-[…]` utility because `.glass-panel` also sets `box-shadow`, and which
|
||
of the two wins would come down to stylesheet order. */
|
||
.glass-panel--composer,
|
||
.glass-panel--composer:focus-within {
|
||
box-shadow: var(--shadow-composer);
|
||
}
|
||
|
||
.glass-panel--composer:focus-within {
|
||
box-shadow: var(--shadow-focus-ring), var(--shadow-composer);
|
||
}
|
||
|
||
/* Radial wash behind the empty-state seal. */
|
||
.brand-seal-glow {
|
||
background: radial-gradient(460px 300px at 50% 44%, var(--color-brand-soft) 0%, transparent 72%);
|
||
}
|
||
|
||
.composer-drop-target-active {
|
||
border-color: var(--color-border-focus);
|
||
box-shadow: var(--shadow-focus-ring), var(--shadow-dropdown);
|
||
}
|
||
|
||
.composer-drop-overlay {
|
||
animation: composer-drop-fade 140ms ease-out, composer-drop-pulse 1.1s ease-in-out infinite;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.composer-drop-overlay {
|
||
animation: none;
|
||
}
|
||
}
|
||
|
||
.app-shell-viewport {
|
||
min-height: 100vh;
|
||
min-height: 100dvh;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
}
|
||
|
||
.app-shell {
|
||
position: relative;
|
||
width: 100%;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.app-shell-backdrop {
|
||
background: var(--color-overlay-scrim);
|
||
}
|
||
|
||
.sidebar-shell {
|
||
position: relative;
|
||
width: var(--sidebar-width);
|
||
flex-shrink: 0;
|
||
overflow: hidden;
|
||
transition: width var(--motion-sidebar-duration) var(--motion-sidebar-easing);
|
||
will-change: width;
|
||
}
|
||
|
||
.sidebar-shell[data-state="closed"] {
|
||
width: var(--sidebar-rail-width);
|
||
}
|
||
|
||
/* While the pointer is driving the width, the animation must be off or every
|
||
frame chases the previous one and the edge lags behind the cursor. */
|
||
.sidebar-shell--resizing,
|
||
.sidebar-shell--resizing .sidebar-panel {
|
||
transition: none;
|
||
}
|
||
|
||
.sidebar-resize-handle {
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
right: 0;
|
||
width: 8px;
|
||
z-index: 10;
|
||
cursor: col-resize;
|
||
background: transparent;
|
||
transition: background-color 140ms ease;
|
||
touch-action: none;
|
||
}
|
||
|
||
.sidebar-resize-handle::after {
|
||
content: "";
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
right: 0;
|
||
width: 2px;
|
||
background: transparent;
|
||
transition: background-color 140ms ease;
|
||
}
|
||
|
||
.sidebar-resize-handle:hover::after,
|
||
.sidebar-resize-handle:focus-visible::after,
|
||
.sidebar-shell--resizing .sidebar-resize-handle::after {
|
||
background: var(--color-brand);
|
||
}
|
||
|
||
.sidebar-resize-handle:focus-visible {
|
||
outline: none;
|
||
}
|
||
|
||
/* Keep the resize cursor and kill text selection for the whole drag, no matter
|
||
what the pointer ends up over once capture takes it outside the handle. */
|
||
body.sidebar-resizing {
|
||
cursor: col-resize;
|
||
user-select: none;
|
||
}
|
||
|
||
body.sidebar-resizing * {
|
||
cursor: col-resize !important;
|
||
}
|
||
|
||
.app-shell--mobile .sidebar-shell {
|
||
position: fixed;
|
||
inset: 0 auto 0 0;
|
||
z-index: 50;
|
||
width: min(88vw, var(--sidebar-width));
|
||
max-width: var(--sidebar-width);
|
||
overflow: visible;
|
||
transform: translateX(-100%);
|
||
transition:
|
||
transform var(--motion-sidebar-duration) var(--motion-sidebar-easing),
|
||
width var(--motion-sidebar-duration) var(--motion-sidebar-easing);
|
||
}
|
||
|
||
.app-shell--mobile .sidebar-shell[data-state="closed"] {
|
||
pointer-events: none;
|
||
}
|
||
|
||
.app-shell--mobile .sidebar-shell[data-state="open"] {
|
||
transform: translateX(0);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.sidebar-panel {
|
||
width: var(--sidebar-width);
|
||
min-width: var(--sidebar-width);
|
||
background-image: var(--sidebar-panel-bg-image);
|
||
background-repeat: no-repeat;
|
||
transition:
|
||
background-color var(--motion-sidebar-duration) var(--motion-sidebar-easing),
|
||
border-color var(--motion-sidebar-duration) var(--motion-sidebar-easing);
|
||
}
|
||
|
||
.sidebar-panel[data-state="closed"] {
|
||
width: var(--sidebar-rail-width);
|
||
min-width: var(--sidebar-rail-width);
|
||
}
|
||
|
||
.app-shell--mobile .sidebar-panel,
|
||
.app-shell--mobile .sidebar-panel[data-state="closed"] {
|
||
width: min(88vw, var(--sidebar-width));
|
||
min-width: min(88vw, var(--sidebar-width));
|
||
box-shadow: var(--shadow-dropdown);
|
||
}
|
||
|
||
.sidebar-toggle-button {
|
||
color: var(--color-text-secondary-a72);
|
||
background: transparent;
|
||
transition:
|
||
color 180ms ease,
|
||
background-color 180ms ease,
|
||
transform 180ms ease;
|
||
border: 0;
|
||
/* No `box-shadow: none` here: Tailwind's ring utilities ARE box-shadows,
|
||
and this unlayered declaration silently killed every `focus-visible:ring-*`
|
||
a caller put on the button — same failure class as the icon font-size. */
|
||
}
|
||
|
||
.sidebar-toggle-button:hover {
|
||
color: var(--color-text-primary-a88);
|
||
background: var(--color-surface-hover-a34);
|
||
}
|
||
|
||
.sidebar-toggle-button:active {
|
||
transform: translateY(0.5px);
|
||
background: var(--color-surface-hover-a54);
|
||
}
|
||
|
||
.sidebar-toggle-button--open {
|
||
color: var(--color-text-secondary-a68);
|
||
}
|
||
|
||
.sidebar-toggle-button--open:hover {
|
||
color: var(--color-text-primary-a82);
|
||
}
|
||
|
||
.sidebar-toggle-button--collapsed {
|
||
color: var(--color-text-primary-a78);
|
||
}
|
||
|
||
.sidebar-toggle-button--collapsed:hover {
|
||
color: var(--color-text-primary);
|
||
}
|
||
|
||
.sidebar-toggle-button--collapsed:active {
|
||
}
|
||
|
||
.sidebar-toggle-icon {
|
||
overflow: visible;
|
||
}
|
||
|
||
.sidebar-toggle-chevron {
|
||
stroke: currentColor;
|
||
transition:
|
||
opacity 180ms ease,
|
||
transform 180ms ease;
|
||
vector-effect: non-scaling-stroke;
|
||
}
|
||
|
||
.sidebar-toggle-chevron {
|
||
opacity: 0.95;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
stroke-width: 1.85;
|
||
transform-origin: 50% 50%;
|
||
}
|
||
|
||
.sidebar-toggle-icon--open .sidebar-toggle-chevron {
|
||
transform: translateX(-0.2px);
|
||
}
|
||
|
||
.sidebar-toggle-button--collapsed .sidebar-toggle-chevron {
|
||
stroke-width: 2;
|
||
transform: translateX(0.4px);
|
||
}
|
||
|
||
.sidebar-copy {
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
transition:
|
||
max-width var(--motion-sidebar-duration) var(--motion-sidebar-easing),
|
||
opacity calc(var(--motion-sidebar-duration) - 40ms) ease,
|
||
transform var(--motion-sidebar-duration) var(--motion-sidebar-easing);
|
||
}
|
||
|
||
.sidebar-copy--visible {
|
||
max-width: 240px;
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.sidebar-copy--hidden {
|
||
max-width: 0;
|
||
opacity: 0;
|
||
transform: translateX(-10px);
|
||
}
|
||
|
||
.sidebar-section {
|
||
overflow: hidden;
|
||
transition:
|
||
max-height var(--motion-sidebar-duration) var(--motion-sidebar-easing),
|
||
opacity calc(var(--motion-sidebar-duration) - 40ms) ease,
|
||
transform var(--motion-sidebar-duration) var(--motion-sidebar-easing);
|
||
}
|
||
|
||
.sidebar-section--visible {
|
||
max-height: 1200px;
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.sidebar-shell,
|
||
.sidebar-panel,
|
||
.sidebar-copy,
|
||
.sidebar-section {
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 767px) {
|
||
.app-shell-main--mobile {
|
||
width: 100%;
|
||
min-width: 0;
|
||
overflow-x: hidden;
|
||
}
|
||
}
|
||
|
||
.markdown-prose .md-table-wrap table {
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
}
|
||
|
||
.markdown-prose {
|
||
--tw-prose-body: var(--color-text-primary);
|
||
--tw-prose-headings: var(--color-text-primary);
|
||
--tw-prose-lead: var(--color-text-secondary);
|
||
--tw-prose-links: var(--color-text-accent);
|
||
--tw-prose-bold: var(--color-text-primary);
|
||
--tw-prose-counters: var(--color-text-tertiary);
|
||
--tw-prose-bullets: var(--color-text-tertiary);
|
||
--tw-prose-hr: var(--color-border);
|
||
--tw-prose-quotes: var(--color-text-primary);
|
||
--tw-prose-quote-borders: var(--color-outline-variant);
|
||
--tw-prose-captions: var(--color-text-secondary);
|
||
--tw-prose-kbd: var(--color-text-secondary);
|
||
--tw-prose-code: var(--color-code-fg);
|
||
--tw-prose-pre-code: var(--color-code-fg);
|
||
--tw-prose-pre-bg: var(--color-code-bg);
|
||
--tw-prose-th-borders: var(--color-border);
|
||
--tw-prose-td-borders: var(--color-border);
|
||
}
|
||
|
||
.markdown-prose .md-table-wrap tbody tr:last-child td {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.markdown-prose a code {
|
||
color: inherit;
|
||
}
|
||
|
||
/* Scrollbar */
|
||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb { background: var(--color-outline); border-radius: 9999px; opacity: 0.3; }
|
||
::-webkit-scrollbar-thumb:hover { opacity: 0.6; }
|
||
|
||
.sidebar-scroll-area {
|
||
padding-bottom: 72px;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: var(--color-outline-a72) transparent;
|
||
}
|
||
|
||
.sidebar-settings-dock {
|
||
z-index: 30;
|
||
background-color: var(--color-surface-sidebar);
|
||
background-image: var(--sidebar-panel-bg-image);
|
||
background-repeat: no-repeat;
|
||
background-position: left bottom;
|
||
}
|
||
|
||
.sidebar-scroll-area::-webkit-scrollbar {
|
||
width: 4px;
|
||
}
|
||
|
||
.sidebar-scroll-area::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
.sidebar-scroll-area::-webkit-scrollbar-thumb {
|
||
background: var(--color-outline-a78);
|
||
border-radius: 9999px;
|
||
}
|
||
|
||
.sidebar-scroll-area::-webkit-scrollbar-thumb:hover {
|
||
background: var(--color-outline-a92);
|
||
}
|
||
|
||
.activity-summary-panel {
|
||
container-type: inline-size;
|
||
}
|
||
|
||
.activity-summary-grid {
|
||
grid-template-columns: minmax(0, 1fr);
|
||
}
|
||
|
||
.activity-summary-metric-primary {
|
||
grid-column: auto;
|
||
}
|
||
|
||
.activity-summary-value {
|
||
overflow-wrap: normal;
|
||
word-break: keep-all;
|
||
}
|
||
|
||
@container (min-width: 360px) {
|
||
.activity-summary-grid {
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
}
|
||
}
|
||
|
||
@container (min-width: 560px) {
|
||
.activity-summary-grid {
|
||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||
}
|
||
|
||
.activity-summary-metric-primary {
|
||
grid-column: auto;
|
||
}
|
||
}
|
||
|
||
.activity-heat-cell {
|
||
position: relative;
|
||
transform: translateZ(0);
|
||
transform-origin: center;
|
||
transition:
|
||
border-color 150ms ease,
|
||
box-shadow 180ms ease,
|
||
filter 180ms ease,
|
||
transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
|
||
}
|
||
|
||
.activity-heat-cell:hover,
|
||
.activity-heat-cell:focus-visible,
|
||
.activity-heat-cell.is-active {
|
||
z-index: 1;
|
||
transform: translateZ(0) scale(1.16);
|
||
box-shadow: var(--shadow-activity-cell-hover);
|
||
filter: saturate(1.08);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.activity-heat-cell {
|
||
transition-duration: 0.01ms !important;
|
||
}
|
||
|
||
.activity-heat-cell:hover,
|
||
.activity-heat-cell:focus-visible,
|
||
.activity-heat-cell.is-active {
|
||
transform: translateZ(0);
|
||
}
|
||
}
|
||
|
||
/* Animations */
|
||
@keyframes shimmer {
|
||
0%, 100% { opacity: 0.4; }
|
||
50% { opacity: 1; }
|
||
}
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
@keyframes pulse-dot {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: 0.3; }
|
||
}
|
||
@keyframes composer-drop-fade {
|
||
from { opacity: 0; transform: scale(0.985); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
@keyframes composer-drop-pulse {
|
||
0%, 100% { opacity: 0.96; }
|
||
50% { opacity: 1; }
|
||
}
|
||
/* Screen entrance from the handoff: 8px rise + 98.5% scale, fading in. */
|
||
@keyframes screen-pop {
|
||
from { opacity: 0; transform: translateY(8px) scale(0.985); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
|
||
.animate-shimmer { animation: shimmer 1.5s ease-in-out infinite; }
|
||
.animate-spin { animation: spin 0.9s linear infinite; }
|
||
.animate-pulse-dot { animation: pulse-dot 1.6s ease-in-out infinite; }
|
||
.animate-screen-pop { animation: screen-pop 250ms cubic-bezier(0.2, 0.8, 0.2, 1); }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.animate-screen-pop { animation: overlay-fade-in 1ms linear; }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
/* Slow the spinner rather than stopping it. A frozen spinner reads as a
|
||
hung UI, which is a worse outcome than the motion it avoids — the
|
||
rotation is the whole signal the component carries. */
|
||
.animate-spin { animation-duration: 2.4s; }
|
||
.animate-shimmer,
|
||
.animate-pulse-dot { animation-duration: 3s; }
|
||
}
|
||
|
||
/* Overlay entrances.
|
||
These replace the `animate-in slide-in-from-*` classes that came from
|
||
`tailwindcss-animate`; that dependency was removed with the shadcn
|
||
rollback, so those classes silently did nothing. Keep them here so the
|
||
set stays greppable and paired with its keyframes. */
|
||
@keyframes overlay-fade-in {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
@keyframes overlay-in-from-top {
|
||
from { opacity: 0; transform: translateY(-4px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes overlay-in-from-bottom {
|
||
from { opacity: 0; transform: translateY(4px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes overlay-in-from-right {
|
||
from { opacity: 0; transform: translateX(12px); }
|
||
to { opacity: 1; transform: translateX(0); }
|
||
}
|
||
.animate-overlay-in { animation: overlay-fade-in 150ms ease-out; }
|
||
.animate-overlay-in-top { animation: overlay-in-from-top 150ms ease-out; }
|
||
.animate-overlay-in-bottom { animation: overlay-in-from-bottom 150ms ease-out; }
|
||
.animate-overlay-in-right { animation: overlay-in-from-right 200ms cubic-bezier(0.22, 1, 0.36, 1); }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.animate-overlay-in,
|
||
.animate-overlay-in-top,
|
||
.animate-overlay-in-bottom,
|
||
.animate-overlay-in-right {
|
||
animation: overlay-fade-in 1ms linear;
|
||
}
|
||
}
|
||
|
||
/* Indeterminate (browser-style) loading bar: a segment that slides across the track */
|
||
@keyframes progress-indeterminate {
|
||
0% { left: -40%; right: 100%; }
|
||
60% { left: 100%; right: -40%; }
|
||
100% { left: 100%; right: -40%; }
|
||
}
|
||
.progress-indeterminate-track {
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.progress-indeterminate-track::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
bottom: 0;
|
||
left: -40%;
|
||
right: 100%;
|
||
background: var(--color-primary);
|
||
animation: progress-indeterminate 1.1s ease-in-out infinite;
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.progress-indeterminate-track::before {
|
||
animation: none;
|
||
left: 0;
|
||
right: 0;
|
||
opacity: 0.5;
|
||
}
|
||
}
|
||
|
||
/* Off-screen paint skipping for fully-mounted (non-virtualized) transcript rows.
|
||
WebKit (Tauri WKWebView) paints complex message DOM ~2x slower than Blink and
|
||
stutters on long worst-case frames; content-visibility lets it skip off-screen
|
||
rows WITHOUT unmounting or re-measuring them. Measured (Playwright WebKit, 79-msg
|
||
session): worst frame 279ms -> 18ms, jank 2 -> 0. Only non-virtualized rows get
|
||
this class — virtualized window items must stay free of content-visibility to
|
||
avoid the zero-height measurement regression in the virtualizer. */
|
||
.chat-render-item--cv {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: auto 300px;
|
||
}
|
||
|
||
@keyframes chat-navigation-target-pulse {
|
||
0% { background: transparent; }
|
||
22% { background: var(--color-chat-navigation-target); }
|
||
100% { background: transparent; }
|
||
}
|
||
|
||
.chat-render-item--navigation-target {
|
||
border-radius: var(--radius-lg);
|
||
animation: chat-navigation-target-pulse 1.4s ease-out;
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.chat-render-item--navigation-target {
|
||
animation: none;
|
||
background: var(--color-chat-navigation-target);
|
||
}
|
||
}
|
||
|
||
/* Same WebKit paint-skipping trick for trace tree rows (see .chat-render-item--cv
|
||
above). Tree rows are fixed-density 34px single-line items, so the intrinsic
|
||
size estimate is exact and scrolling never drifts. */
|
||
.trace-row-cv {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: auto 34px;
|
||
}
|
||
|
||
/* Trace-window variants of the same trick (kept as classes — not inline styles —
|
||
so the touch-H5 override below can reach them). */
|
||
.trace-message-cv {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: auto 120px;
|
||
}
|
||
.trace-list-row-cv {
|
||
content-visibility: auto;
|
||
contain-intrinsic-size: auto 56px;
|
||
}
|
||
|
||
/* ============================================================================
|
||
Touch-H5 (phone browser) scoped fixes.
|
||
|
||
`html[data-touch-h5]` is set before first paint by src/lib/touchH5.ts only
|
||
when the bundle runs in a browser (no Electron host) on a coarse-pointer
|
||
device — WeChat scan-to-open, mobile Safari, etc. Desktop shells and
|
||
desktop browsers never match, so nothing here can regress desktop chat.
|
||
========================================================================== */
|
||
|
||
/* iOS WKWebView zooms the whole page (and never zooms back) when a focused
|
||
form control renders below 16px. 16px controls remove the trigger at the
|
||
root; the JS side additionally caps the viewport scale on iOS. Android has
|
||
no focus-zoom but 16px inputs are the native size there too. */
|
||
html[data-touch-h5] input,
|
||
html[data-touch-h5] textarea,
|
||
html[data-touch-h5] select {
|
||
font-size: 16px;
|
||
}
|
||
|
||
/* Long-press text selection on iOS WebKit fights content-visibility: auto —
|
||
extending a selection into a skipped row forces synchronous layout, which
|
||
makes the selection handles jump or drops the selection entirely. The paint
|
||
savings were measured for the desktop WKWebView's 860px canvas; a phone
|
||
viewport paints far less per row, so correct selection wins here. */
|
||
html[data-touch-h5] .chat-render-item--cv,
|
||
html[data-touch-h5] .trace-row-cv,
|
||
html[data-touch-h5] .trace-message-cv,
|
||
html[data-touch-h5] .trace-list-row-cv {
|
||
content-visibility: visible;
|
||
}
|
||
|
||
/* Size the shell to the *visual* viewport so the composer rides the soft
|
||
keyboard instead of the keyboard covering it (variable published by
|
||
touchH5.ts; falls back to 100dvh before the first sync). The safe-area
|
||
padding pairs with viewport-fit=cover on iOS: keep the composer above the
|
||
home indicator and the header out of the notch, but drop the bottom inset
|
||
while the keyboard is up — the keyboard covers that strip anyway. */
|
||
html[data-touch-h5] .app-shell-viewport {
|
||
min-height: var(--touch-h5-viewport-height, 100dvh);
|
||
height: var(--touch-h5-viewport-height, 100dvh);
|
||
padding-top: env(safe-area-inset-top, 0px);
|
||
padding-bottom: env(safe-area-inset-bottom, 0px);
|
||
padding-left: env(safe-area-inset-left, 0px);
|
||
padding-right: env(safe-area-inset-right, 0px);
|
||
}
|
||
html[data-touch-h5][data-touch-h5-keyboard] .app-shell-viewport {
|
||
padding-bottom: 0px;
|
||
}
|
||
|
||
/* Message action bars (copy / branch) are hover-revealed on desktop. Keep
|
||
them visible whenever the browser is in the narrow H5 shell, even if an
|
||
embedded browser reports no coarse pointer, and retain the touch marker as
|
||
coverage for wider tablets. */
|
||
.app-shell--mobile [data-message-actions],
|
||
html[data-touch-h5] [data-message-actions] {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.app-shell--mobile [data-message-actions] {
|
||
height: 2.75rem;
|
||
}
|
||
|
||
.app-shell--mobile [data-message-actions] > div,
|
||
.app-shell--mobile [data-message-actions] button {
|
||
min-height: 2.75rem;
|
||
}
|
||
|
||
.app-shell--mobile [data-message-actions] button {
|
||
width: 2.75rem;
|
||
}
|
||
|
||
/* Kill the gray flash WKWebView paints over any tapped interactive element;
|
||
components carry their own pressed/hover styling. */
|
||
html[data-touch-h5],
|
||
html[data-touch-h5] * {
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
/* The app shell is a fixed 100dvh layout with inner scroll areas; the body
|
||
itself never scrolls. Stop edge overscroll from rubber-banding the whole
|
||
shell (and WeChat from revealing its pull-down domain banner mid-chat). */
|
||
html[data-touch-h5] body {
|
||
overscroll-behavior-y: none;
|
||
}
|
||
|
||
/* ─── Companion pet window ───────────────────────────────────── */
|
||
|
||
html[data-window-kind='pet'],
|
||
html[data-window-kind='pet'] body,
|
||
html[data-window-kind='pet'] #root {
|
||
background: transparent !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.pet-window-root {
|
||
position: relative;
|
||
box-sizing: border-box;
|
||
width: 100%;
|
||
height: 100vh;
|
||
background: transparent;
|
||
pointer-events: none;
|
||
user-select: none;
|
||
}
|
||
|
||
.pet-window-stack {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
width: 100%;
|
||
height: 100%;
|
||
box-sizing: border-box;
|
||
padding: 0 12px 12px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Dragging clamps against the mascot alone so it can reach every display edge,
|
||
which by construction pushes the rest of the window off-screen. At the top
|
||
edge that is the menu bar eating the activity panel, so the host asks for the
|
||
panel below the mascot instead and the whole stack flips. */
|
||
.pet-window-stack[data-panel-placement='below'] {
|
||
justify-content: flex-start;
|
||
padding: 12px 12px 0;
|
||
}
|
||
|
||
.pet-mascot-button {
|
||
position: relative;
|
||
z-index: 10;
|
||
display: grid;
|
||
flex: 0 0 auto;
|
||
place-items: center;
|
||
margin-bottom: -2px;
|
||
border: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
cursor: grab;
|
||
transform: scale(1);
|
||
transform-origin: 50% 92%;
|
||
transition: transform 160ms ease-out;
|
||
-webkit-app-region: no-drag;
|
||
pointer-events: auto;
|
||
touch-action: none;
|
||
will-change: transform;
|
||
}
|
||
|
||
.pet-mascot-wrap {
|
||
position: relative;
|
||
order: 2;
|
||
display: grid;
|
||
flex: 0 0 auto;
|
||
place-items: center;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.pet-window-stack[data-panel-placement='below'] .pet-mascot-wrap {
|
||
order: 1;
|
||
}
|
||
|
||
.pet-task-badge {
|
||
position: absolute;
|
||
z-index: 12;
|
||
top: 8px;
|
||
right: 2px;
|
||
display: grid;
|
||
min-width: 24px;
|
||
height: 24px;
|
||
place-items: center;
|
||
border: 1px solid rgba(255, 255, 255, 0.72);
|
||
border-radius: 999px;
|
||
padding: 0 6px;
|
||
background: var(--color-brand);
|
||
box-shadow: 0 5px 14px rgba(13, 31, 48, 0.26);
|
||
color: white;
|
||
cursor: pointer;
|
||
font: 650 12px/1 var(--font-body);
|
||
pointer-events: auto;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
|
||
.pet-task-badge:hover {
|
||
background: var(--color-brand-hover);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.pet-task-badge:focus-visible {
|
||
outline: 2px solid var(--color-border-focus);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.pet-mascot-button[data-dragging='true'] {
|
||
cursor: grabbing;
|
||
transform: scale(0.96);
|
||
}
|
||
|
||
.pet-mascot-button:focus-visible {
|
||
outline: 3px solid var(--color-border-focus);
|
||
outline-offset: 3px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.pet-mascot-button::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 18% 8% 8%;
|
||
border-radius: 50%;
|
||
background:
|
||
radial-gradient(circle at 35% 45%, rgba(112, 143, 255, 0.22), transparent 58%),
|
||
radial-gradient(circle at 68% 62%, rgba(245, 151, 193, 0.16), transparent 62%);
|
||
filter: blur(16px);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.pet-sprite-stage {
|
||
position: relative;
|
||
z-index: 1;
|
||
isolation: isolate;
|
||
overflow: visible;
|
||
transform: scale(1.04);
|
||
transform-origin: 50% 88%;
|
||
}
|
||
|
||
.pet-sprite-stage--atlas {
|
||
transform: scale(1.1);
|
||
}
|
||
|
||
.pet-sprite {
|
||
position: relative;
|
||
z-index: 1;
|
||
transform-origin: 50% 88%;
|
||
}
|
||
|
||
.pet-sprite-stage--atlas .pet-sprite {
|
||
filter: drop-shadow(0 8px 12px rgba(35, 27, 54, 0.14));
|
||
}
|
||
|
||
.pet-sprite[data-pet-motion='enabled'] {
|
||
will-change: transform;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='idle'] {
|
||
animation: pet-motion-idle 3.3s ease-in-out infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='running'] {
|
||
animation: pet-motion-work 820ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='running-right'] {
|
||
animation: pet-motion-travel-right 1060ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='running-left'] {
|
||
animation: pet-motion-travel-left 1060ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='waving'] {
|
||
animation: pet-motion-wave 700ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='jumping'] {
|
||
animation: pet-motion-jump 840ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='failed'] {
|
||
animation: pet-motion-fail 1220ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='waiting'] {
|
||
animation: pet-motion-wait 1010ms linear infinite;
|
||
}
|
||
|
||
.pet-sprite-stage--single .pet-sprite[data-pet-motion='enabled'][data-pet-motion-state='review'] {
|
||
animation: pet-motion-review 1030ms linear infinite;
|
||
}
|
||
|
||
.pet-activity-card {
|
||
position: relative;
|
||
z-index: 15;
|
||
box-sizing: border-box;
|
||
order: 1;
|
||
width: calc(100% - 32px);
|
||
max-width: 360px;
|
||
flex: 0 0 auto;
|
||
overflow: visible;
|
||
/* Keep the card, collapse control, and visible mascot as one compact stack. */
|
||
margin-bottom: 12px;
|
||
border: 1px solid rgba(255, 255, 255, 0.76);
|
||
border-radius: 24px;
|
||
padding: 6px 7px;
|
||
background: rgba(255, 255, 255, 0.8);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.68),
|
||
0 12px 30px rgba(30, 25, 44, 0.14);
|
||
backdrop-filter: blur(20px) saturate(1.18);
|
||
-webkit-backdrop-filter: blur(20px) saturate(1.18);
|
||
transition: border-radius 180ms ease, padding 180ms ease;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.pet-activity-card[data-expanded='true'] {
|
||
border-radius: 22px;
|
||
padding-right: 7px;
|
||
}
|
||
|
||
.pet-window-stack[data-panel-placement='below'] .pet-activity-card {
|
||
order: 2;
|
||
margin-top: 12px;
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-activity-card,
|
||
html[data-theme='ink-blue'] .pet-activity-card {
|
||
border-color: rgba(255, 255, 255, 0.12);
|
||
background: rgba(24, 23, 30, 0.78);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||
0 12px 30px rgba(0, 0, 0, 0.24);
|
||
}
|
||
|
||
.pet-session-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
max-height: 52px;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
scrollbar-width: none;
|
||
transition: max-height 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
|
||
.pet-session-list[data-expanded='true'] {
|
||
max-height: 138px;
|
||
}
|
||
|
||
.pet-session-list::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.pet-session-list > [role='listitem'] + [role='listitem'] {
|
||
border-top: 1px solid rgba(72, 67, 82, 0.08);
|
||
}
|
||
|
||
.pet-session-list > [role='listitem'] {
|
||
transition: opacity 150ms ease, transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1);
|
||
}
|
||
|
||
.pet-session-list[data-expanded='false'] > [role='listitem']:nth-child(n + 2) {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transform: translateY(-4px);
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-session-list > [role='listitem'] + [role='listitem'],
|
||
html[data-theme='ink-blue'] .pet-session-list > [role='listitem'] + [role='listitem'] {
|
||
border-top-color: rgba(255, 255, 255, 0.07);
|
||
}
|
||
|
||
.pet-session-row {
|
||
display: flex;
|
||
width: 100%;
|
||
min-height: 52px;
|
||
align-items: center;
|
||
gap: 12px;
|
||
border: 0;
|
||
border-radius: 17px;
|
||
padding: 7px 10px;
|
||
background: transparent;
|
||
color: #25222b;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: background 140ms ease, transform 140ms ease;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
|
||
.pet-session-row:hover {
|
||
background: rgba(255, 255, 255, 0.58);
|
||
}
|
||
|
||
.pet-session-indicator {
|
||
width: 9px;
|
||
height: 9px;
|
||
flex: 0 0 auto;
|
||
border-radius: 999px;
|
||
background: rgba(117, 113, 126, 0.52);
|
||
}
|
||
|
||
.pet-session-indicator.is-waiting {
|
||
background: #f1ad3b;
|
||
}
|
||
|
||
.pet-session-indicator.is-failed {
|
||
background: #ee5a73;
|
||
}
|
||
|
||
.pet-session-indicator.is-review {
|
||
background: #46bf8c;
|
||
}
|
||
|
||
.pet-session-indicator.is-running {
|
||
width: 12px;
|
||
height: 12px;
|
||
border: 2px solid var(--color-primary-fixed-dim);
|
||
border-top-color: var(--color-brand);
|
||
background: transparent;
|
||
animation: pet-status-spin 1s linear infinite;
|
||
}
|
||
|
||
.pet-session-row:active {
|
||
transform: scale(0.985);
|
||
}
|
||
|
||
.pet-session-row:focus-visible,
|
||
.pet-panel-toggle:focus-visible {
|
||
outline: 2px solid var(--color-border-focus);
|
||
outline-offset: -2px;
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-session-row,
|
||
html[data-theme='ink-blue'] .pet-session-row {
|
||
color: rgba(255, 255, 255, 0.94);
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-session-row:hover,
|
||
html[data-theme='ink-blue'] .pet-session-row:hover {
|
||
background: rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.pet-session-copy {
|
||
display: flex;
|
||
min-width: 0;
|
||
flex: 1;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.pet-session-title,
|
||
.pet-session-status {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.pet-session-title {
|
||
font: 650 13px/1.25 var(--font-body);
|
||
}
|
||
|
||
.pet-session-status {
|
||
color: rgba(75, 70, 85, 0.66);
|
||
font: 500 11px/1.25 var(--font-body);
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-session-status,
|
||
html[data-theme='ink-blue'] .pet-session-status {
|
||
color: rgba(236, 232, 243, 0.62);
|
||
}
|
||
|
||
.pet-panel-toggle {
|
||
position: absolute;
|
||
z-index: 13;
|
||
right: 50%;
|
||
bottom: -31px;
|
||
display: grid;
|
||
width: 25px;
|
||
height: 25px;
|
||
place-items: center;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(255, 255, 255, 0.7);
|
||
background: rgba(52, 55, 61, 0.72);
|
||
color: rgba(255, 255, 255, 0.88);
|
||
cursor: pointer;
|
||
transform: translateX(50%);
|
||
transition: background 140ms ease, color 140ms ease, transform 140ms ease;
|
||
-webkit-app-region: no-drag;
|
||
}
|
||
|
||
.pet-activity-card[data-expanded='true'] .pet-panel-toggle {
|
||
top: auto;
|
||
transform: translateX(50%);
|
||
}
|
||
|
||
/* Flipped, the control stays on the mascot-facing side of the card so the card,
|
||
the control, and the mascot remain one compact stack. */
|
||
.pet-window-stack[data-panel-placement='below'] .pet-activity-card .pet-panel-toggle {
|
||
top: -31px;
|
||
bottom: auto;
|
||
}
|
||
|
||
.pet-panel-toggle:hover {
|
||
background: rgba(43, 47, 54, 0.88);
|
||
color: white;
|
||
}
|
||
|
||
.pet-panel-toggle:active {
|
||
transform: translateX(50%) scale(0.94);
|
||
}
|
||
|
||
.pet-activity-card[data-expanded='true'] .pet-panel-toggle:active {
|
||
transform: translateX(50%) scale(0.94);
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-panel-toggle,
|
||
html[data-theme='ink-blue'] .pet-panel-toggle {
|
||
border-color: rgba(255, 255, 255, 0.18);
|
||
background: rgba(36, 39, 45, 0.82);
|
||
color: rgba(239, 235, 246, 0.62);
|
||
}
|
||
|
||
html[data-theme='dark'] .pet-panel-toggle:hover,
|
||
html[data-theme='ink-blue'] .pet-panel-toggle:hover {
|
||
background: rgba(255, 255, 255, 0.09);
|
||
color: white;
|
||
}
|
||
|
||
.pet-error-card {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 7px;
|
||
color: rgba(70, 65, 79, 0.68);
|
||
font-size: 12px;
|
||
}
|
||
|
||
.pet-error-card {
|
||
position: absolute;
|
||
left: 22px;
|
||
bottom: 14px;
|
||
width: 340px;
|
||
border: 1px solid rgba(234, 82, 104, 0.24);
|
||
border-radius: 18px;
|
||
padding: 18px;
|
||
background: rgba(255, 244, 246, 0.9);
|
||
color: #a83f54;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
@keyframes pet-motion-idle {
|
||
0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
|
||
18% { transform: translate3d(0, 0.3%, 0) rotate(-0.15deg) scale(1.004, 0.997); }
|
||
48% { transform: translate3d(0, -1.1%, 0) rotate(0.2deg) scale(0.997, 1.006); }
|
||
72% { transform: translate3d(0, -0.25%, 0) rotate(0deg) scale(1.002, 0.999); }
|
||
}
|
||
|
||
@keyframes pet-motion-work {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
9% {
|
||
transform: translate3d(-0.35%, 0.8%, 0) rotate(-0.5deg) scale(1.01, 0.989);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
29% {
|
||
transform: translate3d(0.8%, -1.7%, 0) rotate(0.65deg) scale(0.992, 1.012);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
54% {
|
||
transform: translate3d(-0.6%, -0.35%, 0) rotate(-0.45deg) scale(1.002, 0.999);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
72% {
|
||
transform: translate3d(0.25%, 0.65%, 0) rotate(0.2deg) scale(1.008, 0.992);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
88% { transform: translate3d(0, -0.25%, 0) rotate(-0.1deg) scale(0.998, 1.003); }
|
||
}
|
||
|
||
@keyframes pet-motion-travel-right {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
8% {
|
||
transform: translate3d(-0.7%, 0.75%, 0) rotate(-1deg) scale(1.012, 0.987);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
25% {
|
||
transform: translate3d(1.2%, -2.1%, 0) rotate(1.25deg) scale(0.989, 1.016);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
50% {
|
||
transform: translate3d(0.35%, -0.45%, 0) rotate(0.55deg) scale(1.002, 0.999);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
69% {
|
||
transform: translate3d(-0.25%, 0.7%, 0) rotate(-0.35deg) scale(1.01, 0.99);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
86% { transform: translate3d(0.2%, -0.35%, 0) rotate(0.2deg) scale(0.998, 1.003); }
|
||
}
|
||
|
||
@keyframes pet-motion-travel-left {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
8% {
|
||
transform: translate3d(0.7%, 0.75%, 0) rotate(1deg) scale(1.012, 0.987);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
25% {
|
||
transform: translate3d(-1.2%, -2.1%, 0) rotate(-1.25deg) scale(0.989, 1.016);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
50% {
|
||
transform: translate3d(-0.35%, -0.45%, 0) rotate(-0.55deg) scale(1.002, 0.999);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
69% {
|
||
transform: translate3d(0.25%, 0.7%, 0) rotate(0.35deg) scale(1.01, 0.99);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
86% { transform: translate3d(-0.2%, -0.35%, 0) rotate(-0.2deg) scale(0.998, 1.003); }
|
||
}
|
||
|
||
@keyframes pet-motion-wave {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
11% {
|
||
transform: translate3d(0.4%, 0.45%, 0) rotate(0.7deg) scale(1.006, 0.994);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
34% {
|
||
transform: translate3d(-0.7%, -1.45%, 0) rotate(-1.35deg) scale(0.996, 1.007);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
61% {
|
||
transform: translate3d(0.55%, -0.6%, 0) rotate(0.95deg) scale(1.001);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
82% { transform: translate3d(-0.15%, 0.2%, 0) rotate(-0.3deg) scale(1.002, 0.998); }
|
||
}
|
||
|
||
@keyframes pet-motion-jump {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
11% {
|
||
transform: translate3d(0, 1.6%, 0) rotate(-0.35deg) scale(1.035, 0.956);
|
||
animation-timing-function: cubic-bezier(0.12, 0.8, 0.25, 1);
|
||
}
|
||
29% {
|
||
transform: translate3d(0.45%, -5.8%, 0) rotate(1deg) scale(0.974, 1.035);
|
||
animation-timing-function: cubic-bezier(0.4, 0, 0.65, 1);
|
||
}
|
||
51% {
|
||
transform: translate3d(0, -7.5%, 0) rotate(0.3deg) scale(0.988, 1.015);
|
||
animation-timing-function: cubic-bezier(0.3, 0, 0.75, 0.55);
|
||
}
|
||
73% {
|
||
transform: translate3d(-0.25%, -2%, 0) rotate(-0.6deg) scale(1.005, 0.995);
|
||
animation-timing-function: cubic-bezier(0.1, 0.75, 0.25, 1);
|
||
}
|
||
82% {
|
||
transform: translate3d(0, 1.15%, 0) rotate(0.2deg) scale(1.04, 0.96);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
92% { transform: translate3d(0, -0.45%, 0) rotate(-0.1deg) scale(0.992, 1.008); }
|
||
}
|
||
|
||
@keyframes pet-motion-fail {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
12% {
|
||
transform: translate3d(0.5%, -0.8%, 0) rotate(0.8deg) scale(0.994, 1.007);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
33% {
|
||
transform: translate3d(-0.7%, 1.1%, 0) rotate(-1.5deg) scale(1.018, 0.978);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
58% { transform: translate3d(-0.3%, 0.75%, 0) rotate(-0.75deg) scale(1.009, 0.991); }
|
||
78% { transform: translate3d(0.15%, -0.25%, 0) rotate(0.25deg) scale(0.997, 1.004); }
|
||
}
|
||
|
||
@keyframes pet-motion-wait {
|
||
0%, 100% { transform: translate3d(0, 0, 0) rotate(0deg) scale(1); }
|
||
21% { transform: translate3d(-0.4%, -0.65%, 0) rotate(-0.35deg) scale(0.998, 1.004); }
|
||
49% { transform: translate3d(0.45%, -1.25%, 0) rotate(0.45deg) scale(0.995, 1.007); }
|
||
76% { transform: translate3d(-0.1%, 0.2%, 0) rotate(-0.1deg) scale(1.004, 0.997); }
|
||
}
|
||
|
||
@keyframes pet-motion-review {
|
||
0%, 100% {
|
||
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
||
animation-timing-function: cubic-bezier(0.55, 0.06, 0.68, 0.19);
|
||
}
|
||
10% {
|
||
transform: translate3d(0, 0.55%, 0) rotate(-0.25deg) scale(1.008, 0.992);
|
||
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
31% {
|
||
transform: translate3d(-0.45%, -1.35%, 0) rotate(-0.55deg) scale(0.994, 1.009);
|
||
animation-timing-function: cubic-bezier(0.45, 0, 0.55, 1);
|
||
}
|
||
57% {
|
||
transform: translate3d(0.55%, -0.55%, 0) rotate(0.5deg) scale(1.002, 0.999);
|
||
animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
|
||
}
|
||
80% { transform: translate3d(-0.15%, 0.3%, 0) rotate(-0.15deg) scale(1.005, 0.995); }
|
||
}
|
||
|
||
@keyframes pet-status-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.pet-mascot-button,
|
||
.pet-activity-card,
|
||
.pet-session-row,
|
||
.pet-panel-toggle,
|
||
.pet-sprite,
|
||
.pet-session-indicator.is-running {
|
||
animation: none !important;
|
||
transition: none !important;
|
||
}
|
||
}
|