Files
vhaudiquet 81da26b955 feat(rhema-code): add the colors setting with four palettes
The stored `colors` key names a palette — rhema (the default,
Jadeite), aquamarine, black-and-white, or dracula — and the
Appearance page of /settings cycles it live, like the theme and
icon toggles beside it. Each palette carries a dark and a light
appearance under the existing `theme` key; dracula's light side is
derived, the official palette ships only dark.

The appearance decision moves off token comparison: Theme now
answers is_dark() from the ground/text luminance, so palettes
beyond the original two get the right per-profile fallback tones.
2026-10-17 04:28:31 +08:00

396 lines
7.1 KiB
CSS

/*
* The Jadeite tokens, dark theme — lifted token for token from
* crates/rhema-code/src/palette.rs `DARK` so the web claw reads as the
* same tool as the TUI. The ground is deliberately jade-tinted, not
* slate; saturated color stays on the accent and the status set.
*/
:root {
--ground: #0f1413;
--surface: #171d1b;
--elevated: #1f2624;
--sunken: #0a0f0e;
--border: #28312e;
--border-strong: #3d4844;
--text: #eef3f1;
--text2: #b9c6c1;
--text3: #87948f;
--accent: #35a882;
--accent-text: #64c9a3;
--on-accent: #07110d;
--running: #64c9a3;
--ok: #4fbf94;
--warn: #d9a643;
--error: #e4687a;
--info: #8fa6b8;
--sans: ui-sans-serif, system-ui, 'Segoe UI', Roboto, 'Helvetica Neue',
Arial, sans-serif;
--mono: ui-monospace, 'JetBrains Mono', 'Fira Code', 'SF Mono', Menlo,
Consolas, monospace;
}
* {
box-sizing: border-box;
}
html,
body,
#__nuxt {
height: 100%;
}
body {
margin: 0;
background: var(--ground);
color: var(--text);
font-family: var(--sans);
font-size: 13.5px;
line-height: 1.55;
-webkit-font-smoothing: antialiased;
}
/* Values, keys, and anything the claw itself says render mono; the UI
chrome around them renders sans. */
code,
pre,
.mono {
font-family: var(--mono);
font-size: 0.93em;
}
/* Section labels: the one repeated typographic voice across panels. */
.label {
font-family: var(--sans);
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.13em;
text-transform: uppercase;
color: var(--text3);
}
/* --- controls ------------------------------------------------------- */
button {
font: inherit;
color: var(--text);
background: var(--elevated);
border: 1px solid var(--border-strong);
border-radius: 6px;
padding: 5px 14px;
cursor: pointer;
}
button:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent-text);
}
button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
button.primary {
background: var(--accent);
border-color: var(--accent);
color: var(--on-accent);
font-weight: 600;
}
button.primary:hover:not(:disabled) {
background: var(--accent-text);
border-color: var(--accent-text);
color: var(--on-accent);
}
button.ghost {
background: none;
border-color: transparent;
color: var(--text3);
}
button.ghost:hover:not(:disabled) {
color: var(--accent-text);
}
input,
select,
textarea {
font: inherit;
color: var(--text);
background: var(--sunken);
border: 1px solid var(--border);
border-radius: 6px;
padding: 6px 10px;
}
input:hover,
select:hover,
textarea:hover {
border-color: var(--border-strong);
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible {
outline: 1px solid var(--accent);
outline-offset: 0;
}
input::placeholder,
textarea::placeholder {
color: var(--text3);
}
/* --- surfaces ------------------------------------------------------- */
/* The one card: a titled container of setting rows or static facts. */
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.card > .card-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.card > .card-head .title {
font-weight: 700;
font-size: 14px;
}
.card > .card-head .meta {
color: var(--text3);
font-size: 12px;
min-width: 0;
text-align: right;
}
.card > .card-body {
padding: 0;
}
/* Rows inside a card body separate by hairline, not by gaps. */
.card > .card-body > * + * {
border-top: 1px solid var(--border);
}
.card > .card-body.flush {
padding: 8px 16px;
}
.card > .card-body.flush > * + * {
border-top: none;
}
/* --- badges ---------------------------------------------------------- */
.badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 1px 8px;
border-radius: 999px;
border: 1px solid var(--border-strong);
color: var(--text3);
font-size: 11px;
font-family: var(--mono);
white-space: nowrap;
}
.badge.accent {
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
color: var(--accent-text);
background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.badge.ok {
border-color: color-mix(in srgb, var(--ok) 55%, transparent);
color: var(--ok);
}
.badge.warn {
border-color: color-mix(in srgb, var(--warn) 55%, transparent);
color: var(--warn);
}
.badge.error {
border-color: color-mix(in srgb, var(--error) 55%, transparent);
color: var(--error);
}
/* --- banners --------------------------------------------------------- */
/* Banners: the single inline error/notice surface. Never toasts, never
console-only — claw down, 401, and stream drops all land here. */
.banner {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border: 1px solid var(--border-strong);
border-left: 3px solid var(--info);
border-radius: 6px;
background: var(--surface);
color: var(--text2);
}
.banner.error {
border-left-color: var(--error);
color: var(--text);
}
.banner.warn {
border-left-color: var(--warn);
}
.banner .banner-text {
flex: 1;
min-width: 0;
overflow-wrap: anywhere;
}
/* --- markdown -------------------------------------------------------- */
/* Rendered agent replies (AppMarkdown): markdown-it output mounted with
v-html, so only generated tags show up here. Blocks separate by small
margins; headings stay close to body size, a chat reply not a page. */
.md > :first-child {
margin-top: 0;
}
.md > :last-child {
margin-bottom: 0;
}
.md p,
.md ul,
.md ol,
.md pre,
.md blockquote,
.md table {
margin: 0 0 8px;
}
.md ul,
.md ol {
padding-left: 20px;
}
.md li {
margin: 2px 0;
}
.md li > ul,
.md li > ol {
margin-bottom: 0;
}
.md h1,
.md h2,
.md h3,
.md h4,
.md h5,
.md h6 {
margin: 14px 0 6px;
font-weight: 700;
line-height: 1.3;
}
.md h1 {
font-size: 16px;
}
.md h2 {
font-size: 15px;
}
.md h3 {
font-size: 14px;
}
.md h4,
.md h5,
.md h6 {
font-size: 13.5px;
}
.md a {
color: var(--accent-text);
}
.md code {
background: var(--sunken);
border: 1px solid var(--border);
border-radius: 4px;
padding: 1px 5px;
}
.md pre {
padding: 10px 12px;
background: var(--sunken);
border: 1px solid var(--border);
border-radius: 6px;
overflow-x: auto;
}
/* The global code rule shrinks em-relative twice inside a pre; undo it. */
.md pre code {
background: none;
border: none;
padding: 0;
font-size: inherit;
}
.md blockquote {
padding: 2px 12px;
border-left: 3px solid var(--border-strong);
color: var(--text2);
}
.md hr {
border: none;
border-top: 1px solid var(--border);
}
.md table {
border-collapse: collapse;
}
.md th,
.md td {
border: 1px solid var(--border-strong);
padding: 3px 10px;
text-align: left;
}
.md th {
background: var(--surface);
}
.md img {
max-width: 100%;
}
/* While the turn streams, the cursor rides the end of the last block. */
.md.streaming > :last-child::after {
content: '▊';
margin-left: 1px;
color: var(--accent-text);
animation: md-blink 1.1s steps(1) infinite;
}
@keyframes md-blink {
50% {
opacity: 0;
}
}