mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-18 00:23:25 -06:00
3dff1727b1
chat links used --accent while user bubbles are filled from --accent-subtle (and peer bubbles from a per-sender hue), so links sat on their own hue at 3.95:1 worst case. New --link/--link-hover tokens derive from each palette's audited --accent-hover and clear WCAG AA on every bubble surface and sender hue; the hover opacity fade, which lowered contrast, is replaced by a color shift.
1085 lines
31 KiB
CSS
1085 lines
31 KiB
CSS
:root {
|
|
/* Background - Deep, rich dark with layered depth */
|
|
--bg: #0e1015;
|
|
--bg-accent: #13151b;
|
|
--bg-elevated: #191c24;
|
|
--bg-hover: #1f2330;
|
|
--bg-muted: #1f2330;
|
|
|
|
/* Card / Surface - Clear hierarchy between levels */
|
|
--card: #161920;
|
|
--card-foreground: #f0f0f2;
|
|
--card-highlight: rgba(255, 255, 255, 0.04);
|
|
--popover: #191c24;
|
|
--popover-foreground: #f0f0f2;
|
|
|
|
/* Panel */
|
|
--panel: #0e1015;
|
|
--panel-strong: #191c24;
|
|
--panel-hover: #1f2330;
|
|
--chrome: rgba(14, 16, 21, 0.96);
|
|
--chrome-strong: rgba(14, 16, 21, 0.98);
|
|
|
|
/* Text - Clean contrast
|
|
* Keep --muted AA on dark surfaces; do not dim muted body text with opacity.
|
|
* --text targets ~10:1 on --bg #0e1015 (10.1; 9.3 on --card) — the dark
|
|
* comfort band is ~10-12 and brighter body text halates on dark. */
|
|
|
|
--text: #bcbcc0;
|
|
--text-strong: #f4f4f5;
|
|
--chat-text: #bcbcc0;
|
|
--muted: #8b8b94;
|
|
--muted-strong: #898990;
|
|
--muted-foreground: #8b8b94;
|
|
|
|
/* Border - Whisper-thin, barely there */
|
|
--border: #1e2028;
|
|
--border-strong: #2e3040;
|
|
--border-hover: #3e4050;
|
|
--input: #1e2028;
|
|
--ring: #ff5c5c;
|
|
|
|
/* Shared rail chrome. Light and dark themes inherit their actual colors
|
|
through --border/--accent while keeping one geometry contract. */
|
|
--rail-header-height: 48px;
|
|
--rail-header-padding-start: 12px;
|
|
--rail-header-padding-end: 8px;
|
|
--rail-header-action-size: 28px;
|
|
--rail-header-action-glyph-size: 16px;
|
|
--rail-header-action-color: var(--muted);
|
|
--rail-header-action-hover-color: var(--text);
|
|
--rail-header-action-active-color: var(--accent);
|
|
--rail-header-action-disabled-opacity: 0.4;
|
|
--rail-header-action-gap: 2px;
|
|
--rail-header-copy-gap: 2px;
|
|
--rail-header-background: var(--bg);
|
|
--rail-header-eyebrow-size: var(--control-ui-text-xs);
|
|
--rail-header-eyebrow-letter-spacing: 0.04em;
|
|
--rail-header-title-size: var(--control-ui-text-sm);
|
|
--rail-header-title-weight: 600;
|
|
--rail-divider-color: var(--border);
|
|
--rail-divider-size: 1px;
|
|
--rail-divider-active-size: 2px;
|
|
--rail-resizer-size: 4px;
|
|
|
|
/*
|
|
* Accent - Punchy signature red
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #0e1015, relative luminance ≈ 0.005):
|
|
* --accent #ff5c5c on bg: 6.3:1 AA text ✓
|
|
* --primary-foreground #ffffff on #d13c3c button: 4.75:1 AA ✓
|
|
* --primary-foreground #ffffff on #c22e2e hover: 5.64:1 AA ✓
|
|
* --destructive-foreground #fafafa on #d32f2f button: 4.77:1 AA ✓
|
|
* --destructive-foreground #fafafa on #bc2a2a hover: 5.74:1 AA ✓
|
|
* --card/--popover/--secondary foreground pairs: 15.4:1 / 14.9:1 / 15.4:1 ✓
|
|
* --selection-fg #ffffff on --selection-bg #005fcc: 6.0:1 AA ✓
|
|
*/
|
|
--accent: #ff5c5c;
|
|
--accent-hover: #ff7070;
|
|
--accent-muted: #ff5c5c;
|
|
--accent-subtle: rgba(255, 92, 92, 0.1);
|
|
--accent-foreground: #fafafa;
|
|
--accent-glow: rgba(255, 92, 92, 0.2);
|
|
|
|
/* Link text keeps the accent hue but cannot be --accent: chat paints user
|
|
bubbles from that same token, so a link there fights its own background.
|
|
Derived, not per-theme — --accent-hover is every palette's audited lifted
|
|
accent and custom themes derive it too, and the extra 15% of --text buys
|
|
the margin the sender-tinted peer bubbles need (the lightest tint hue on
|
|
openknot sits at 4.3:1 against bare --accent-hover). Both steps move toward
|
|
the text color, which raises contrast in light and dark alike, so hover is
|
|
feedback that never costs readability. */
|
|
--link: color-mix(in srgb, var(--accent-hover) 85%, var(--text));
|
|
--link-hover: color-mix(in srgb, var(--link) 82%, var(--text));
|
|
--selection-bg: #005fcc;
|
|
--selection-fg: #ffffff;
|
|
--primary: #d13c3c;
|
|
--primary-hover: #c22e2e;
|
|
--primary-foreground: #ffffff;
|
|
|
|
/* Secondary */
|
|
--secondary: #161920;
|
|
--secondary-foreground: #f0f0f2;
|
|
--accent-2: #14b8a6;
|
|
--accent-2-muted: rgba(20, 184, 166, 0.7);
|
|
--accent-2-subtle: rgba(20, 184, 166, 0.1);
|
|
|
|
/* Theme-invariant presentation colors. */
|
|
--button-icon-bg: #ffffff;
|
|
--brand-logo-chip-bg: #ffffff;
|
|
--qr-bg: #ffffff;
|
|
--qr-text: #444444;
|
|
--media-bg: #000000;
|
|
--media-foreground: #ffffff;
|
|
--exec-highlight: #f97316;
|
|
--moon-highlight: #fef3c7;
|
|
--moon-base: #fbbf24;
|
|
--workshop-warm: #ff8a5c;
|
|
--workshop-teal: #14b8a6;
|
|
--workshop-cyan: #0891b2;
|
|
|
|
/* Provider and product brand colors never follow the active UI theme. */
|
|
--brand-codex: #10a37f;
|
|
--brand-claude: #d97757;
|
|
--brand-google: #4285f4;
|
|
|
|
/*
|
|
* Semantic status
|
|
*
|
|
* Each `--x` doubles as label text on its own `--x-subtle` tint, so the pair
|
|
* is one contrast decision: the 8% tint composites onto the surface and the
|
|
* label must still clear WCAG AA 4.5:1 on the lightest dark surface
|
|
* (dash `--card` #221a16, worse than claw `--card` #161920). Keep every
|
|
* `-subtle`/`-muted` rgba in sync with its base hex — they are the same
|
|
* color at 8%/75%, and re-tinting one without the other silently drops the
|
|
* pair below AA. Bases stay literal hex: `widget-theme.ts` publishes them to
|
|
* MCP app guest documents, where a `color-mix(... var(--x) ...)` string
|
|
* would not resolve.
|
|
*
|
|
* WCAG 2.1 AA audit (label on own subtle tint, worst of --card / --bg):
|
|
* --ok #22c55e 6.66:1 ✓ --warn #f59e0b 6.93:1 ✓
|
|
* --danger #f87171 5.52:1 ✓ --info #60a5fa 5.97:1 ✓
|
|
*
|
|
* Bounded to --card and --bg. A status label can also land on --bg-hover,
|
|
* --bg-muted, --panel-hover or --input, which this audit does not cover.
|
|
*/
|
|
--ok: #22c55e;
|
|
--ok-muted: rgba(34, 197, 94, 0.75);
|
|
--ok-subtle: rgba(34, 197, 94, 0.08);
|
|
--destructive: #ef4444;
|
|
--destructive-foreground: #fafafa;
|
|
--warn: #f59e0b;
|
|
--warn-muted: rgba(245, 158, 11, 0.75);
|
|
--warn-subtle: rgba(245, 158, 11, 0.08);
|
|
--danger: #f87171;
|
|
--danger-muted: rgba(248, 113, 113, 0.75);
|
|
--danger-subtle: rgba(248, 113, 113, 0.08);
|
|
--info: #60a5fa;
|
|
--info-subtle: rgba(96, 165, 250, 0.08);
|
|
--warn-bright: #f59e0b;
|
|
--warn-strong: #d97706;
|
|
--danger-solid-hover: #dc2626;
|
|
--pr-merged: #a78bfa;
|
|
/* Run state needs its own token: --accent is red in every shipped theme and
|
|
sits on top of --danger, so a spinning ring reads as a failed session.
|
|
Graphical indicator, judged against WCAG 1.4.11 (3:1), not the label audit
|
|
above: #14b8a6 is 7.7:1 on --bg. */
|
|
--run: #14b8a6;
|
|
|
|
/* Focus */
|
|
--focus: rgba(255, 92, 92, 0.2);
|
|
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 80%, transparent);
|
|
--focus-glow: 0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 16px var(--accent-glow);
|
|
|
|
/* Grid */
|
|
--grid-line: rgba(255, 255, 255, 0.03);
|
|
|
|
/* GitHub syntax palette shared by highlighted code and file previews. */
|
|
--hljs-keyword: #ff8a80;
|
|
--hljs-number: #79c0ff;
|
|
--hljs-string: #a5d6ff;
|
|
--hljs-title: #d2a8ff;
|
|
--hljs-type: #ffa657;
|
|
--hljs-attribute: #7ee787;
|
|
--hljs-meta: #c9d1d9;
|
|
--hljs-deletion: #ffa198;
|
|
--hljs-addition: #7ee787;
|
|
|
|
/* Theme transition */
|
|
--theme-switch-x: 50%;
|
|
--theme-switch-y: 50%;
|
|
|
|
/* Typography */
|
|
--mono:
|
|
"JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, monospace;
|
|
--font-body: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
--font-display: var(--font-body);
|
|
--control-ui-text-scale: 1;
|
|
--control-ui-text-xs: calc(11px * var(--control-ui-text-scale));
|
|
--control-ui-text-sm: calc(12px * var(--control-ui-text-scale));
|
|
--control-ui-text-md: calc(14px * var(--control-ui-text-scale));
|
|
--control-ui-text-lg: calc(16px * var(--control-ui-text-scale));
|
|
--control-ui-input-text-size: max(16px, calc(14px * var(--control-ui-text-scale)));
|
|
--chat-text-size: var(--control-ui-text-md);
|
|
|
|
/* Shadows - Subtle, layered depth */
|
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.25);
|
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.3);
|
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.4);
|
|
--shadow-xl: 0 24px 48px rgba(0, 0, 0, 0.5);
|
|
--shadow-glow: 0 0 24px var(--accent-glow);
|
|
|
|
/* Spacing scale. Settings surfaces use these (see
|
|
ui/docs/design-system/settings-design.md); elsewhere raw px padding/gap is
|
|
grandfathered — adopt the scale when rewriting a rule, don't mass-migrate. */
|
|
--space-1: 4px;
|
|
--space-2: 8px;
|
|
--space-3: 12px;
|
|
--space-4: 16px;
|
|
--space-5: 20px;
|
|
--space-6: 24px;
|
|
--space-7: 32px;
|
|
--space-8: 40px;
|
|
|
|
/* One inset shared by every widget frame (chat preview panel, board card)
|
|
so agent-authored widget documents meet identical breathing room on all
|
|
surfaces. Board layout math mirrors this value numerically. */
|
|
--widget-frame-inset: var(--space-3);
|
|
|
|
/* Radii - Slightly larger for modern feel */
|
|
--radius-sm: 6px;
|
|
--radius-md: 10px;
|
|
--radius-lg: 14px;
|
|
--radius-xl: 20px;
|
|
--radius-full: 9999px;
|
|
--radius: 10px;
|
|
|
|
/* Menu items stay optically parallel to their panel edge: radius 6 + padding 4
|
|
= panel radius 10. Shared knobs keep transient menus from drifting again. */
|
|
--menu-radius: var(--radius-md);
|
|
--menu-item-radius: var(--radius-sm);
|
|
--menu-item-height: 28px;
|
|
--menu-padding: 4px;
|
|
|
|
/* Transitions - Crisp and responsive */
|
|
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
--duration-fast: 100ms;
|
|
--duration-normal: 180ms;
|
|
--duration-slow: 300ms;
|
|
--safe-area-top: env(safe-area-inset-top, 0px);
|
|
--safe-area-right: env(safe-area-inset-right, 0px);
|
|
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
|
|
--safe-area-left: env(safe-area-inset-left, 0px);
|
|
|
|
/* Layering */
|
|
--z-dropdown: 100;
|
|
--z-toast: 2000;
|
|
|
|
/* Custom select chevron. Data URIs cannot read var(--muted), so the stroke
|
|
color is baked per theme mode; light mode overrides the whole image. */
|
|
--select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
|
|
|
color-scheme: dark;
|
|
}
|
|
|
|
/* Menu actions follow the platform 44px touch-target contract for coarse pointers. */
|
|
@media (pointer: coarse) {
|
|
:root {
|
|
--menu-item-height: 44px;
|
|
}
|
|
}
|
|
|
|
/* The claw family deepens only the text-bearing destructive fill. */
|
|
:root[data-theme="dark"] {
|
|
--destructive: #d32f2f;
|
|
--destructive-hover: #bc2a2a;
|
|
}
|
|
|
|
/* Light theme tokens apply to every light-mode family. */
|
|
:root[data-theme-mode="light"] {
|
|
/*
|
|
* Warm paper light mode - terracotta accent on ivory
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #faf9f7, relative luminance ≈ 0.941):
|
|
* --accent #bd4531 contrast ≈ 4.9:1 AA text ✓
|
|
* --primary-foreground #ffffff on #bd4531 button: 5.2:1 AA ✓
|
|
* --text #403c35 ≈ 10.4:1 AAA ✓, --muted #6e6960 ≈ 5.2:1 AA ✓
|
|
*/
|
|
--bg: #faf9f7;
|
|
--bg-accent: #f4f1ec;
|
|
--bg-elevated: #ffffff;
|
|
--bg-hover: #efebe4;
|
|
--bg-muted: #efebe4;
|
|
--bg-content: #f4f1ec;
|
|
|
|
--card: #ffffff;
|
|
--card-foreground: #211e1a;
|
|
--card-highlight: rgba(60, 42, 24, 0.03);
|
|
--popover: #ffffff;
|
|
--popover-foreground: #211e1a;
|
|
|
|
--panel: #faf9f7;
|
|
--panel-strong: #f4f1ec;
|
|
--panel-hover: #e9e4dc;
|
|
--chrome: rgba(250, 249, 247, 0.96);
|
|
--chrome-strong: rgba(250, 249, 247, 0.98);
|
|
|
|
--text: #403c35;
|
|
--text-strong: #211e1a;
|
|
--chat-text: #403c35;
|
|
--muted: #6e6960;
|
|
--muted-strong: #56524a;
|
|
--muted-foreground: #6e6960;
|
|
|
|
--border: #e8e4dc;
|
|
--border-strong: #d6d0c5;
|
|
--border-hover: #b4ac9e;
|
|
--input: #e8e4dc;
|
|
--ring: #bd4531;
|
|
|
|
--accent: #bd4531;
|
|
--accent-hover: #a83c29;
|
|
--accent-muted: #bd4531;
|
|
--accent-subtle: rgba(189, 69, 49, 0.08);
|
|
--accent-foreground: #ffffff;
|
|
--accent-glow: rgba(189, 69, 49, 0.12);
|
|
--primary: #bd4531;
|
|
--primary-foreground: #ffffff;
|
|
|
|
--secondary: #f4f1ec;
|
|
--secondary-foreground: #403c35;
|
|
--accent-2: #0d9488;
|
|
--accent-2-muted: rgba(13, 148, 136, 0.75);
|
|
--accent-2-subtle: rgba(13, 148, 136, 0.08);
|
|
|
|
/*
|
|
* Semantic status — see the `:root` block for the pairing rule. Light mode
|
|
* is bounded by `--bg` (#faf9f7, and the darker dash-light #f7f2ec) rather
|
|
* than the white `--card`, so a token that passes on a card can still fail
|
|
* on the page behind it.
|
|
*
|
|
* WCAG 2.1 AA audit (label on own subtle tint, worst of --card / --bg):
|
|
* --ok #166534 5.70:1 ✓ --warn #92400e 5.66:1 ✓
|
|
* --danger #b91c1c 5.10:1 ✓ --info #1d4ed8 5.34:1 ✓
|
|
*
|
|
* Bounded to --card and --bg. Hover and input surfaces are not covered, and
|
|
* one gap is reachable: .plugins-row-message--error sits on --danger-subtle
|
|
* over a hovered row, which is 4.28:1 under dash-light. That is an
|
|
* improvement on the 3.23:1 it replaced, not a regression, but it is not AA.
|
|
*/
|
|
--ok: #166534;
|
|
--ok-muted: rgba(22, 101, 52, 0.75);
|
|
--ok-subtle: rgba(22, 101, 52, 0.08);
|
|
--destructive: #dc2626;
|
|
--destructive-foreground: #fafafa;
|
|
--warn: #92400e;
|
|
--warn-muted: rgba(146, 64, 14, 0.75);
|
|
--warn-subtle: rgba(146, 64, 14, 0.08);
|
|
--danger: #b91c1c;
|
|
--danger-muted: rgba(185, 28, 28, 0.75);
|
|
--danger-subtle: rgba(185, 28, 28, 0.08);
|
|
--info: #1d4ed8;
|
|
--info-subtle: rgba(29, 78, 216, 0.08);
|
|
--pr-merged: #7c3aed;
|
|
/* See the dark-theme note: run state stays off --accent. #0d9488 is 3.6:1 on
|
|
--bg, above the 3:1 WCAG 1.4.11 bar for a non-text indicator. */
|
|
--run: #0d9488;
|
|
|
|
--focus: rgba(189, 69, 49, 0.15);
|
|
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 70%, transparent);
|
|
--focus-glow: 0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 12px var(--accent-glow);
|
|
|
|
--grid-line: rgba(60, 42, 24, 0.05);
|
|
|
|
--hljs-keyword: #cf222e;
|
|
--hljs-number: #0550ae;
|
|
--hljs-string: #0a3069;
|
|
--hljs-title: #8250df;
|
|
--hljs-type: #953800;
|
|
--hljs-attribute: #116329;
|
|
--hljs-meta: #57606a;
|
|
|
|
/* Light shadows - Subtle, warm-tinted */
|
|
--shadow-sm: 0 1px 2px rgba(60, 42, 24, 0.05);
|
|
--shadow-md: 0 4px 12px rgba(60, 42, 24, 0.07);
|
|
--shadow-lg: 0 12px 28px rgba(60, 42, 24, 0.09);
|
|
--shadow-xl: 0 24px 48px rgba(60, 42, 24, 0.11);
|
|
--shadow-glow: 0 0 20px var(--accent-glow);
|
|
|
|
--select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23444' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
|
|
|
|
color-scheme: light;
|
|
}
|
|
|
|
/* Scrollbar - visible on light backgrounds */
|
|
:root[data-theme-mode="light"]::-webkit-scrollbar-thumb,
|
|
:root[data-theme-mode="light"] ::-webkit-scrollbar-thumb {
|
|
background: rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
:root[data-theme-mode="light"]::-webkit-scrollbar-thumb:hover,
|
|
:root[data-theme-mode="light"] ::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
/* Theme families override accent tokens while keeping shared surfaces/layout. */
|
|
:root[data-theme="openknot"] {
|
|
/*
|
|
* Accent — crimson red on true black
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #080808, relative luminance ≈ 0.003):
|
|
* --accent #e5243b L ≈ 0.118 contrast ≈ 5.8:1 AA text ✓
|
|
* --accent-hover #f03e52 L ≈ 0.163 contrast ≈ 7.2:1 ✓
|
|
* --primary-foreground #fafafa on #d92a3f button: 4.62:1 AA ✓
|
|
* --primary-foreground #fafafa on #c22537 hover: 5.57:1 AA ✓
|
|
* --destructive-foreground #fafafa on #d32f2f button: 4.77:1 AA ✓
|
|
* --destructive-foreground #fafafa on #bc2a2a hover: 5.74:1 AA ✓
|
|
* focus-ring at 70% opacity: ≈ 3.4:1 against bg ✓ (non-text 3:1 required)
|
|
*/
|
|
--ring: #e5243b;
|
|
--accent: #e5243b;
|
|
--accent-hover: #f03e52;
|
|
--accent-muted: #e5243b;
|
|
--accent-subtle: rgba(229, 36, 59, 0.12);
|
|
--accent-glow: rgba(229, 36, 59, 0.24);
|
|
--primary: #d92a3f;
|
|
--primary-hover: #c22537;
|
|
--primary-foreground: #fafafa;
|
|
|
|
/* Deepen only the text-bearing destructive fill; keep danger accents vivid. */
|
|
--destructive: #d32f2f;
|
|
--destructive-hover: #bc2a2a;
|
|
|
|
/* Focus — crimson ring */
|
|
--focus: rgba(229, 36, 59, 0.2);
|
|
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 70%, transparent);
|
|
--focus-glow: 0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 16px var(--accent-glow);
|
|
|
|
/* Surfaces — true black canvas */
|
|
--bg: #080808;
|
|
--bg-accent: #0d0d0f;
|
|
--bg-elevated: #141416;
|
|
--bg-hover: #1a1a1e;
|
|
--bg-muted: #1a1a1e;
|
|
|
|
--card: #111113;
|
|
--card-foreground: #f0f0f2;
|
|
--card-highlight: rgba(255, 255, 255, 0.03);
|
|
--popover: #141416;
|
|
--popover-foreground: #f0f0f2;
|
|
|
|
--panel: #080808;
|
|
--panel-strong: #141416;
|
|
--panel-hover: #1a1a1e;
|
|
--chrome: rgba(8, 8, 8, 0.96);
|
|
--chrome-strong: rgba(8, 8, 8, 0.98);
|
|
|
|
/* Text — high contrast on black */
|
|
--text: #e0e0e2;
|
|
--text-strong: #f5f5f7;
|
|
--chat-text: #e0e0e2;
|
|
--muted: #8a8a94;
|
|
--muted-strong: #888892;
|
|
--muted-foreground: #8a8a94;
|
|
|
|
/* Borders — whisper-thin, barely visible */
|
|
--border: #1a1a1e;
|
|
--border-strong: #2a2a30;
|
|
--border-hover: #3a3a42;
|
|
--input: #1a1a1e;
|
|
|
|
--secondary: #111113;
|
|
--secondary-foreground: #f0f0f2;
|
|
--accent-2: #8b1a2b;
|
|
--accent-2-muted: rgba(139, 26, 43, 0.7);
|
|
--accent-2-subtle: rgba(139, 26, 43, 0.12);
|
|
|
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
|
|
--shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.6);
|
|
|
|
--grid-line: rgba(255, 255, 255, 0.025);
|
|
}
|
|
|
|
:root[data-theme="openknot-light"] {
|
|
/*
|
|
* Accent — deep red on cool white
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #f9f9fb, relative luminance ≈ 0.941):
|
|
* --accent #c41e30 L ≈ 0.078 contrast ≈ 7.5:1 AAA text ✓
|
|
* --accent-hover #a8192a L ≈ 0.054 contrast ≈ 9.1:1 ✓
|
|
* --primary-foreground #ffffff on #c41e30 button: 5.0:1 AA ✓
|
|
* focus-ring at 60% opacity: ≈ 3.2:1 against bg ✓ (non-text 3:1 required)
|
|
*/
|
|
--ring: #c41e30;
|
|
--accent: #c41e30;
|
|
--accent-hover: #a8192a;
|
|
--accent-muted: #c41e30;
|
|
--accent-subtle: rgba(196, 30, 48, 0.08);
|
|
--accent-glow: rgba(196, 30, 48, 0.12);
|
|
--primary: #c41e30;
|
|
--primary-foreground: #ffffff;
|
|
|
|
/* Surfaces — cool white with subtle warmth */
|
|
--bg: #f9f9fb;
|
|
--bg-accent: #f2f2f5;
|
|
--bg-elevated: #ffffff;
|
|
--bg-hover: #eaeaef;
|
|
--bg-muted: #eaeaef;
|
|
--bg-content: #f2f2f5;
|
|
|
|
--card: #ffffff;
|
|
--card-foreground: #18181b;
|
|
--card-highlight: rgba(0, 0, 0, 0.02);
|
|
--popover: #ffffff;
|
|
--popover-foreground: #18181b;
|
|
|
|
--panel: #f9f9fb;
|
|
--panel-strong: #f2f2f5;
|
|
--panel-hover: #e4e4ea;
|
|
--chrome: rgba(249, 249, 251, 0.96);
|
|
--chrome-strong: rgba(249, 249, 251, 0.98);
|
|
|
|
/* Text — crisp dark on white */
|
|
--text: #3a3a42;
|
|
--text-strong: #18181b;
|
|
--chat-text: #3a3a42;
|
|
--muted: #68676f;
|
|
--muted-strong: #52525a;
|
|
--muted-foreground: #68676f;
|
|
|
|
/* Borders — clean, minimal */
|
|
--border: #e2e2e8;
|
|
--border-strong: #ccccd4;
|
|
--border-hover: #adadb8;
|
|
--input: #e2e2e8;
|
|
|
|
--secondary: #f2f2f5;
|
|
--secondary-foreground: #3a3a42;
|
|
--accent-2: #7a1420;
|
|
--accent-2-muted: rgba(122, 20, 32, 0.75);
|
|
--accent-2-subtle: rgba(122, 20, 32, 0.08);
|
|
|
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06);
|
|
--shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.08);
|
|
|
|
--grid-line: rgba(0, 0, 0, 0.04);
|
|
}
|
|
|
|
:root[data-theme="dash"] {
|
|
/*
|
|
* Accent — chocolate brown on deep cocoa
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #1a1210, relative luminance ≈ 0.0069):
|
|
* --accent #b47840 L ≈ 0.235 contrast ≈ 5.0:1 AA text ✓ (floor: #a8703c → 4.4:1, fails)
|
|
* --accent-hover #c8885a L ≈ 0.306 contrast ≈ 6.3:1 ✓
|
|
* --primary-foreground #1a1210 on #b47840 button: 5.0:1 AA ✓
|
|
* --primary-foreground #1a1210 on #c8885a hover: 6.26:1 AA ✓
|
|
* --destructive-foreground #fafafa on #d32f2f button: 4.77:1 AA ✓
|
|
* --destructive-foreground #fafafa on #bc2a2a hover: 5.74:1 AA ✓
|
|
* focus-ring at 80% opacity: ≈ 3.5:1 against bg ✓ (non-text 3:1 required)
|
|
*/
|
|
--ring: #b47840;
|
|
--accent: #b47840;
|
|
--accent-hover: #c8885a;
|
|
--accent-muted: #b47840;
|
|
--accent-subtle: rgba(180, 120, 64, 0.16);
|
|
--accent-glow: rgba(180, 120, 64, 0.24);
|
|
--primary: #b47840;
|
|
--primary-foreground: #1a1210;
|
|
|
|
/* Deepen only the text-bearing destructive fill; keep danger accents vivid. */
|
|
--destructive: #d32f2f;
|
|
--destructive-hover: #bc2a2a;
|
|
|
|
/* Focus — match chocolate accent, slightly higher ring opacity for dark bg visibility */
|
|
--focus: rgba(180, 120, 64, 0.2);
|
|
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 3px color-mix(in srgb, var(--ring) 80%, transparent);
|
|
--focus-glow: 0 0 0 2px var(--bg), 0 0 0 3px var(--ring), 0 0 16px var(--accent-glow);
|
|
|
|
/* Surfaces — deep cocoa tones */
|
|
--bg: #1a1210;
|
|
--bg-accent: #201816;
|
|
--bg-elevated: #28201c;
|
|
--bg-hover: #302822;
|
|
--bg-muted: #302822;
|
|
|
|
--card: #221a16;
|
|
--card-foreground: #ece0d8;
|
|
--card-highlight: rgba(255, 240, 225, 0.04);
|
|
--popover: #28201c;
|
|
--popover-foreground: #ece0d8;
|
|
|
|
--panel: #1a1210;
|
|
--panel-strong: #28201c;
|
|
--panel-hover: #302822;
|
|
--chrome: rgba(26, 18, 16, 0.96);
|
|
--chrome-strong: rgba(26, 18, 16, 0.98);
|
|
|
|
--text: #d8c8b8;
|
|
--text-strong: #f0e4da;
|
|
--chat-text: #d8c8b8;
|
|
--muted: #a18f80;
|
|
--muted-strong: #a08f7f;
|
|
--muted-foreground: #a18f80;
|
|
|
|
--border: #302418;
|
|
--border-strong: #443828;
|
|
--border-hover: #5a4c3a;
|
|
--input: #302418;
|
|
|
|
--secondary: #221a16;
|
|
--secondary-foreground: #ece0d8;
|
|
--accent-2: #a88050;
|
|
--accent-2-muted: rgba(168, 128, 80, 0.7);
|
|
--accent-2-subtle: rgba(168, 128, 80, 0.12);
|
|
|
|
--shadow-sm: 0 1px 2px rgba(10, 6, 4, 0.35);
|
|
--shadow-md: 0 4px 16px rgba(10, 6, 4, 0.45);
|
|
--shadow-lg: 0 12px 32px rgba(10, 6, 4, 0.55);
|
|
|
|
--grid-line: rgba(255, 240, 225, 0.03);
|
|
}
|
|
|
|
:root[data-theme="dash-light"] {
|
|
/*
|
|
* Accent — deep chocolate on warm parchment
|
|
*
|
|
* WCAG 2.1 AA audit (bg = #f7f2ec, relative luminance ≈ 0.893):
|
|
* --accent #6e4828 L ≈ 0.081 contrast ≈ 7.2:1 AAA text ✓
|
|
* --accent-hover #5c3c20 L ≈ 0.054 contrast ≈ 8.9:1 ✓
|
|
* --primary-foreground #ffffff on #6e4828 button: 8.0:1 AAA ✓
|
|
* focus-ring at 70% opacity: ≈ 3.4:1 against parchment bg ✓
|
|
*/
|
|
--ring: #6e4828;
|
|
--accent: #6e4828;
|
|
--accent-hover: #5c3c20;
|
|
--accent-muted: #6e4828;
|
|
--accent-subtle: rgba(110, 72, 40, 0.12);
|
|
--accent-glow: rgba(110, 72, 40, 0.16);
|
|
--primary: #6e4828;
|
|
--primary-foreground: #ffffff;
|
|
|
|
/* Surfaces — warm parchment tones */
|
|
--bg: #f7f2ec;
|
|
--bg-accent: #f0e8e0;
|
|
--bg-elevated: #ffffff;
|
|
--bg-hover: #e8ddd2;
|
|
--bg-muted: #e8ddd2;
|
|
--bg-content: #f0e8e0;
|
|
|
|
--card: #ffffff;
|
|
--card-foreground: #2c2118;
|
|
--card-highlight: rgba(80, 50, 20, 0.02);
|
|
--popover: #ffffff;
|
|
--popover-foreground: #2c2118;
|
|
|
|
--panel: #f7f2ec;
|
|
--panel-strong: #f0e8e0;
|
|
--panel-hover: #e0d4c8;
|
|
--chrome: rgba(247, 242, 236, 0.96);
|
|
--chrome-strong: rgba(247, 242, 236, 0.98);
|
|
|
|
--text: #4a3828;
|
|
--text-strong: #2c2118;
|
|
--chat-text: #4a3828;
|
|
--muted: #725d4d;
|
|
--muted-strong: #604838;
|
|
--muted-foreground: #725d4d;
|
|
|
|
--border: #ddd0c2;
|
|
--border-strong: #c8b8a6;
|
|
--border-hover: #b0a090;
|
|
--input: #ddd0c2;
|
|
|
|
--secondary: #f0e8e0;
|
|
--secondary-foreground: #4a3828;
|
|
--accent-2: #6a4e30;
|
|
--accent-2-muted: rgba(106, 78, 48, 0.75);
|
|
--accent-2-subtle: rgba(106, 78, 48, 0.1);
|
|
|
|
--shadow-sm: 0 1px 2px rgba(60, 40, 20, 0.06);
|
|
--shadow-md: 0 4px 12px rgba(60, 40, 20, 0.08);
|
|
--shadow-lg: 0 12px 28px rgba(60, 40, 20, 0.1);
|
|
|
|
--grid-line: rgba(80, 50, 20, 0.04);
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.sr-only {
|
|
position: absolute;
|
|
width: 1px;
|
|
height: 1px;
|
|
margin: -1px;
|
|
padding: 0;
|
|
overflow: hidden;
|
|
clip: rect(0, 0, 0, 0);
|
|
clip-path: inset(50%);
|
|
white-space: nowrap;
|
|
border: 0;
|
|
}
|
|
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
/* App shell: inner panes scroll, the root never does — in every host
|
|
(browser tab, PWA, Mac app WKWebView). WKWebView drag/rubber-band scrolls
|
|
the document even when nothing overflows, dragging the whole shell with
|
|
it; `clip` also blocks programmatic root scrolls (focus/scrollIntoView)
|
|
that `hidden` would let strand the shell off-screen. */
|
|
overflow: clip;
|
|
overscroll-behavior: none;
|
|
/* The app supplies its own pressed and selected states; WebKit's tap flash
|
|
reads as foreign chrome on top of them. */
|
|
-webkit-tap-highlight-color: transparent;
|
|
/* iOS WKWebView font boosting inflates rendered text without re-running
|
|
layout, so labels spill out of fixed-size chrome (welcome chips, badges).
|
|
Locking text-size-adjust keeps rendered metrics equal to layout metrics. */
|
|
-webkit-text-size-adjust: 100%;
|
|
text-size-adjust: 100%;
|
|
}
|
|
|
|
a,
|
|
button,
|
|
label,
|
|
summary,
|
|
input,
|
|
select,
|
|
textarea,
|
|
[role="button"] {
|
|
touch-action: manipulation;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
font: 400 14px/1.55 var(--font-body);
|
|
letter-spacing: -0.01em;
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
/* App chrome behaves like native controls: dragging across labels, tiles,
|
|
and navigation must not create accidental text selections. Text-entry
|
|
surfaces and document-like content opt back in below or in route CSS. */
|
|
user-select: none;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
}
|
|
|
|
/* iOS Safari zooms the viewport when a focused text control is under 16px;
|
|
important keeps feature-local typography from silently dropping this floor.
|
|
Controls that manage their own touch size declare it via
|
|
--control-ui-touch-input-size; the floor never caps above 16px. */
|
|
@media (pointer: coarse) {
|
|
input,
|
|
select,
|
|
textarea {
|
|
font-size: max(16px, var(--control-ui-touch-input-size, 1em)) !important;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1600px) {
|
|
body {
|
|
font-size: 15px;
|
|
}
|
|
}
|
|
|
|
/* Theme transition */
|
|
@keyframes theme-circle-transition {
|
|
0% {
|
|
clip-path: circle(0% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%));
|
|
}
|
|
100% {
|
|
clip-path: circle(150% at var(--theme-switch-x, 50%) var(--theme-switch-y, 50%));
|
|
}
|
|
}
|
|
|
|
html.theme-transition {
|
|
view-transition-name: theme;
|
|
}
|
|
|
|
html.theme-transition::view-transition-old(theme) {
|
|
mix-blend-mode: normal;
|
|
animation: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
html.theme-transition::view-transition-new(theme) {
|
|
mix-blend-mode: normal;
|
|
z-index: 2;
|
|
animation: theme-circle-transition 0.4s var(--ease-out) forwards;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
html.theme-transition::view-transition-old(theme),
|
|
html.theme-transition::view-transition-new(theme) {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
|
|
openclaw-app {
|
|
display: block;
|
|
position: relative;
|
|
z-index: 1;
|
|
height: 100%;
|
|
}
|
|
|
|
@media (display-mode: standalone) {
|
|
@supports (height: 100dvh) {
|
|
html,
|
|
body {
|
|
height: 100dvh;
|
|
}
|
|
}
|
|
|
|
body {
|
|
position: fixed;
|
|
inset: 0;
|
|
padding-top: var(--safe-area-top);
|
|
padding-right: var(--safe-area-right);
|
|
padding-bottom: var(--safe-area-bottom);
|
|
padding-left: var(--safe-area-left);
|
|
}
|
|
}
|
|
|
|
/* Cursor policy. A browser tab gets the pointer hand, the only hover affordance
|
|
a page owns; installed/app-like windows are desktop chrome and keep the arrow.
|
|
Controls consume --cursor-action rather than hardcoding a cursor, and real
|
|
hyperlinks keep the UA pointer in every mode. */
|
|
/* stylelint-disable-next-line no-duplicate-selectors -- cursor policy block, see comment above */
|
|
:root {
|
|
--cursor-action: pointer;
|
|
}
|
|
|
|
/* Separate from the standalone block above, which owns viewport and safe-area
|
|
insets; widening its condition would change those too. `fullscreen` is absent
|
|
on purpose: it is a transient presentation state any window can enter, and
|
|
the manifest installs this UI as `standalone`. */
|
|
@media (display-mode: standalone),
|
|
(display-mode: minimal-ui),
|
|
(display-mode: window-controls-overlay) {
|
|
:root {
|
|
--cursor-action: default;
|
|
}
|
|
}
|
|
|
|
/* Native app hosts are installed desktop windows too, but they embed a plain
|
|
web view that reports `display-mode: browser`; they announce themselves with
|
|
these `<html>` markers instead (all three, as layout.css already matches). */
|
|
html:is(.openclaw-native-macos, .openclaw-native-nav, .openclaw-native-web-chrome) {
|
|
--cursor-action: default;
|
|
}
|
|
|
|
/* Generic actionable controls, at element/attribute specificity only so the
|
|
component rules that own semantic cursors (not-allowed, disabled, grab,
|
|
resize, deliberate `default` on non-actionable elements) still win. */
|
|
button,
|
|
summary,
|
|
select,
|
|
[role="button"],
|
|
[role="checkbox"],
|
|
[role="menuitem"],
|
|
[role="menuitemcheckbox"],
|
|
[role="menuitemradio"],
|
|
[role="option"],
|
|
[role="radio"],
|
|
[role="switch"],
|
|
[role="tab"],
|
|
input:is(
|
|
[type="button"],
|
|
[type="checkbox"],
|
|
[type="color"],
|
|
[type="file"],
|
|
[type="radio"],
|
|
[type="range"],
|
|
[type="reset"],
|
|
[type="submit"]
|
|
) {
|
|
cursor: var(--cursor-action);
|
|
}
|
|
|
|
a {
|
|
color: var(--accent);
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
}
|
|
|
|
a:hover {
|
|
text-decoration-thickness: 2px;
|
|
}
|
|
|
|
/* Web Awesome dropdown items ship `cursor: pointer` on their shadow host.
|
|
Document rules beat `:host`, so route them through the policy here — and
|
|
re-add the disabled cue this override would otherwise clobber. */
|
|
wa-dropdown-item {
|
|
cursor: var(--cursor-action);
|
|
}
|
|
|
|
wa-dropdown-item[disabled] {
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Keep Web Awesome's checkbox rail trailing so it cannot shift menu icons and labels. */
|
|
wa-dropdown-item[checkbox-adjacent] {
|
|
padding-inline-start: 1em;
|
|
}
|
|
|
|
wa-dropdown-item[type="checkbox"]::part(checkmark) {
|
|
order: 1;
|
|
margin-inline: 0.75em 0;
|
|
}
|
|
|
|
wa-dropdown-item[type="checkbox"] {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
button,
|
|
input,
|
|
textarea,
|
|
select {
|
|
font: inherit;
|
|
color: inherit;
|
|
}
|
|
|
|
/* One accent for every native check control; without this, surfaces that
|
|
forget a local override render the browser's stock blue. */
|
|
input[type="checkbox"],
|
|
input[type="radio"] {
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
input,
|
|
textarea,
|
|
[contenteditable]:not([contenteditable="false"]) {
|
|
user-select: text;
|
|
}
|
|
|
|
::selection {
|
|
background: var(--selection-bg);
|
|
color: var(--selection-fg);
|
|
}
|
|
|
|
/* Main/document surfaces keep a normal scrollbar hit target. Dense sidebars
|
|
override this inherited size locally so their row actions retain room. */
|
|
::-webkit-scrollbar {
|
|
width: var(--scrollbar-size, 12px);
|
|
height: var(--scrollbar-size, 12px);
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: rgba(255, 255, 255, 0.14);
|
|
}
|
|
|
|
/* Animations - Polished with spring feel */
|
|
@keyframes rise {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes fade-in {
|
|
from {
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
@keyframes menu-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(6px) scale(0.985);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0) scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes scale-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.95);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
@keyframes dashboard-enter {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% {
|
|
background-position: -200% 0;
|
|
}
|
|
100% {
|
|
background-position: 200% 0;
|
|
}
|
|
}
|
|
|
|
/* Skeleton loading primitives */
|
|
.skeleton {
|
|
background: linear-gradient(90deg, var(--bg-muted) 25%, var(--bg-hover) 50%, var(--bg-muted) 75%);
|
|
background-size: 200% 100%;
|
|
animation: shimmer 1.5s ease-in-out infinite;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.skeleton-line {
|
|
height: 14px;
|
|
border-radius: var(--radius-sm);
|
|
}
|
|
|
|
.skeleton-line--short {
|
|
width: 40%;
|
|
}
|
|
|
|
.skeleton-line--medium {
|
|
width: 65%;
|
|
}
|
|
|
|
.skeleton-line--long {
|
|
width: 85%;
|
|
}
|
|
|
|
@keyframes pulse-subtle {
|
|
0%,
|
|
100% {
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
|
|
/* Focus visible styles */
|
|
:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
:focus:not(:focus-visible) {
|
|
outline: none;
|
|
}
|