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.
396 lines
7.1 KiB
CSS
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;
|
|
}
|
|
}
|