Files
HomerandClaude Fable 5.1 881a222f5f The look from the server's tokens, pinned; the frame and bar follow the page's theme
ui/tokens.css is no longer a hand copy that drifts: scripts/fetch-tokens.sh
takes the server's tokens.css at a git ref and tokens.lock.json pins it, as
fetch-cli.sh does for the CLI. build.rs reads the two themes' --bg and --border
out of it, so no colour is written in Rust. The content view reports the page's
theme by a navigation the app refuses (llembas-theme:<name>), and the window,
the title bar and the first-run page are painted in it and remember it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-18 03:38:31 +02:00

154 lines
5.4 KiB
CSS

/* The first-run page. Every colour, size and space is a token from tokens.css (the server's,
fetched by scripts/fetch-tokens.sh); the one value of this page's own is the card's width. */
* { box-sizing: border-box; }
:root {
/* The card's widest: a layout fact of this page, not a token the server has. */
--card-max: 40rem;
}
html, body { margin: 0; min-height: 100%; }
body {
display: grid;
place-items: center;
min-height: 100vh;
padding: var(--sp-4);
background: var(--bg);
color: var(--ink);
font: var(--text-base) / var(--leading-normal) var(--font-body);
}
.card {
width: min(100%, var(--card-max));
display: grid;
gap: var(--sp-6);
padding: var(--sp-8);
background: var(--surface);
border: var(--border-w) solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-block-lg);
}
.brand { display: grid; grid-template-columns: var(--sp-10) 1fr; gap: var(--sp-3); align-items: center; }
.brand img { width: var(--sp-10); height: var(--sp-10); }
h1 { margin: 0; font: 600 var(--text-xl) / var(--leading-tight) var(--font-ui); }
h1 b { color: var(--leaf); font-weight: 700; }
/* A section's heading, in brackets as on the server's screens: `[ SERVER ]`. The brackets are
drawn here so the words alone are what gets translated. */
h2 {
margin: 0 0 var(--sp-3);
font: 600 var(--text-sm) / var(--leading-tight) var(--font-ui);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--accent);
}
h2::before { content: "[ "; }
h2::after { content: " ]"; }
p { margin: 0; }
.muted { color: var(--ink-muted); font-size: var(--text-sm); }
/* A field is three stacked parts on fixed rows: label, control row, hint. */
.field { display: grid; grid-template-rows: auto var(--control-h) auto; gap: var(--sp-2); }
label, .label { font: var(--text-sm) / 1.4 var(--font-ui); color: var(--ink-muted); }
.hint { font-size: var(--text-sm); color: var(--ink-muted); min-height: 1.4em; }
.hint.is-error, .status.is-error { color: var(--warning); }
.row { display: grid; grid-template-columns: 1fr auto; gap: var(--sp-2); }
.row--buttons { grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr)); margin-top: var(--sp-3); }
input {
height: var(--control-h);
min-width: 0;
padding: 0 var(--control-px);
font: var(--text-base) var(--font-ui);
color: var(--ink);
background: var(--bg);
border: var(--border-w) solid var(--border-strong);
border-radius: var(--radius);
}
input:focus-visible, .btn:focus-visible {
outline: var(--outline-w) solid var(--accent);
outline-offset: var(--outline-w);
}
.btn {
height: var(--control-h);
padding: 0 var(--sp-4);
font: 600 var(--text-sm) var(--font-ui);
color: var(--ink);
background: var(--surface);
border: var(--border-w) solid var(--border-strong);
border-radius: var(--radius);
box-shadow: var(--shadow-block);
cursor: pointer;
}
.btn:hover { background: var(--surface-hover); }
.btn:active { transform: translate(var(--press), var(--press)); box-shadow: none; }
.btn:disabled { opacity: 0.55; cursor: default; }
.btn--primary { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.btn--primary:hover { background: var(--accent-hover); }
.status { font: var(--text-sm) / 1.4 var(--font-ui); margin-top: var(--sp-3); }
.status.is-ok { color: var(--leaf); }
@media (max-width: 30rem) {
.card { padding: var(--sp-4); gap: var(--sp-4); }
.row { grid-template-columns: 1fr; grid-template-rows: var(--control-h) var(--control-h); }
.field { grid-template-rows: auto auto auto; }
}
/* The folder list: every row the same two tracks, the path cut from the left (its end is the
part that tells folders apart), the full path in the title. */
.folders { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-2); }
.folders li {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--sp-2);
align-items: center;
min-height: var(--control-h);
padding: 0 0 0 var(--control-px);
background: var(--bg);
border: var(--border-w) solid var(--border);
border-radius: var(--radius);
}
.folders .path {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
direction: rtl;
text-align: left;
font: var(--text-sm) var(--font-ui);
}
.folders .path bdi { direction: ltr; unicode-bidi: isolate; }
.folders .btn {
height: calc(var(--control-h) - 2 * var(--border-w));
box-shadow: none;
border-width: 0 0 0 var(--border-w);
}
.folders .empty { padding: var(--control-px); color: var(--warning); font-size: var(--text-sm); }
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
grid-auto-rows: auto var(--control-h) auto;
column-gap: var(--sp-4);
row-gap: var(--sp-2);
margin-top: var(--sp-4);
}
/* Each field spans three shared rows (label, control, hint), so the controls of side-by-side
fields sit on one line whatever their labels and hints do. */
.settings-grid .field { grid-row: span 3; grid-template-rows: subgrid; }
select {
height: var(--control-h);
min-width: 0;
padding: 0 var(--control-px-sm);
font: var(--text-sm) var(--font-ui);
color: var(--ink);
background: var(--bg);
border: var(--border-w) solid var(--border-strong);
border-radius: var(--radius);
}
.check { display: flex; align-items: center; gap: var(--sp-2); color: var(--ink); }
.check input { width: var(--check-size); height: var(--check-size); margin: 0; accent-color: var(--accent); }