Files
LLeMbas/src/lembas/web/static/css/app.css
T
Jaroslav BenešandClaude Opus 5 aa0bbe524a An admin inspector on the right of the chat
A third child of .shell, opening and closing like the sidebar opposite it,
showing the system message that would go out, the tools offered, what the
last reply cost, and the whole request body as JSON.

Rebuilt, not recorded. Recording every request would store a copy of the
growing conversation against every message -- quadratic in chat length -- and
the thing an administrator debugging a bad answer actually wants is what the
current configuration produces. The panel says exactly that at the top, so
nobody mistakes it for forensics.

Owner-checked and admin-checked, not admin alone. permissions.resolve giving
an admin everything is about configuration, which they can grant themselves
anyway; reading someone's conversation is a different act, and it is why
sharing.visible_to has no admin branch. An inspector that could dump any
user's transcript would be that branch under another name.

No new JavaScript. app.js already delegates [data-toggle], and
hx-trigger="intersect once" makes the load lazy for free: a hidden element
never intersects, so the request fires the first time it is opened and never
on a page load nobody looked at.

Image data URIs are replaced before dumping -- fidelity is the point, but not
several megabytes of base64 in the DOM. Everything renders through normal
escaping and never |safe: this JSON is full of model output, search results
and uploaded documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 00:51:57 +02:00

949 lines
26 KiB
CSS

/*
Application styles.
Rules here resolve colour, spacing and radius through the variables in
tokens.css and never hard-code a value. Layout is flexbox and grid only --
no framework, no preprocessor, no build step.
*/
/* --- Reset ---------------------------------------------------------------- */
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body {
height: 100%;
}
/*
The `hidden` attribute has to win.
The browser's own rule is `[hidden] { display: none }`, which any component
rule setting `display` outranks -- `.btn` is `display: inline-flex`, so a
button hidden from JavaScript stayed visible. That is not a styling nit: it
is how the Stop button came to sit permanently beside Send. Anything toggled
with `hidden` anywhere in the application depends on this line.
*/
[hidden] {
display: none !important;
}
body {
margin: 0;
font-family: var(--font-body);
font-size: var(--text-base);
line-height: var(--leading-normal);
color: var(--ink);
background: var(--bg);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5, h6 {
margin: 0;
font-family: var(--font-display);
font-weight: 600;
line-height: var(--leading-tight);
letter-spacing: 0.01em;
}
p { margin: 0 0 var(--sp-4); }
p:last-child { margin-bottom: 0; }
a {
color: var(--accent);
text-decoration-color: color-mix(in srgb, var(--accent) 40%, transparent);
text-underline-offset: 2px;
}
a:hover { color: var(--accent-hover); }
button, input, textarea, select {
font: inherit;
color: inherit;
}
/* A single, consistent focus ring. Never remove it without a replacement. */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }
.visually-hidden {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
/* --- Icons ---------------------------------------------------------------- */
.icon {
width: 1.25em;
height: 1.25em;
flex: none;
fill: none;
stroke: currentColor;
stroke-width: 1.7;
stroke-linecap: round;
stroke-linejoin: round;
}
.icon--sm { width: 1em; height: 1em; }
.icon--lg { width: 1.5em; height: 1.5em; }
/* The leaf is a filled silhouette, not a stroked pictogram. */
.icon--leaf { fill: currentColor; stroke: none; }
/* --- Buttons ---------------------------------------------------------------
Every variant is the same height and vertically centres its contents, so a
row of mixed buttons lines up without per-instance nudging. Icon buttons are
square at that height rather than a different shape.
*/
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--sp-2);
height: var(--control-h);
padding: 0 var(--control-px);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface-raised);
color: var(--ink);
font-size: var(--text-sm);
font-weight: 500;
line-height: 1;
cursor: pointer;
white-space: nowrap;
text-decoration: none;
transition: background var(--transition-fast), border-color var(--transition-fast),
color var(--transition-fast);
}
.btn:hover:not(:disabled) {
background: var(--surface-hover);
border-color: var(--border-strong);
color: var(--ink);
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn--primary {
background: var(--accent);
border-color: var(--accent);
color: var(--accent-ink);
}
.btn--primary:hover:not(:disabled) {
background: var(--accent-hover);
border-color: var(--accent-hover);
color: var(--accent-ink);
}
.btn--danger { color: var(--danger); }
.btn--danger:hover:not(:disabled) {
background: var(--danger-soft);
border-color: var(--danger);
color: var(--danger);
}
.btn--ghost { background: transparent; border-color: transparent; }
.btn--ghost:hover:not(:disabled) { background: var(--surface-hover); border-color: transparent; }
/* Square, and the same height as everything beside it. */
.btn--icon {
width: var(--control-h);
padding: 0;
background: transparent;
border-color: transparent;
color: var(--ink-muted);
}
.btn--icon:hover:not(:disabled) { background: var(--surface-hover); color: var(--ink); }
.btn--icon.btn--primary {
color: var(--accent-ink);
background: var(--accent);
border-color: var(--accent);
}
.btn--sm { height: var(--control-h-sm); padding: 0 var(--control-px-sm); font-size: var(--text-xs); }
.btn--sm.btn--icon { width: var(--control-h-sm); padding: 0; }
.btn--lg { height: var(--control-h-lg); padding: 0 var(--sp-5); font-size: var(--text-base); }
.btn--block { width: 100%; }
.btn--grow { flex: 1; }
/* Rows of buttons: gap and alignment in one place, not per instance. */
.btn-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.btn-row--end { justify-content: flex-end; }
.spacer { flex: 1; }
/* --- Forms -----------------------------------------------------------------
Inputs share the button height, so a control row is flush by construction.
*/
.field { margin-bottom: var(--sp-4); }
.field:last-child { margin-bottom: 0; }
.field__label {
display: block;
margin-bottom: var(--sp-2);
font-size: var(--text-sm);
font-weight: 500;
color: var(--ink);
}
.field__hint {
margin-top: var(--sp-2);
font-size: var(--text-xs);
color: var(--ink-faint);
line-height: var(--leading-normal);
}
.input,
.select {
width: 100%;
height: var(--control-h);
padding: 0 var(--control-px);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg-sunken);
color: var(--ink);
font-size: var(--text-sm);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.textarea {
width: 100%;
padding: var(--sp-2) var(--control-px);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg-sunken);
color: var(--ink);
font-size: var(--text-sm);
line-height: var(--leading-normal);
resize: vertical;
min-height: 5rem;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.input:focus,
.textarea:focus,
.select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-faint); }
/*
File inputs.
A file input is two things in one box -- a button the browser draws and the
chosen filename beside it -- and neither inherits anything useful. Left alone
with `.input`, the padding applies to the whole control so the button sits
hard against the left edge while the text floats off its centre line.
So: no horizontal padding on the control, the button styled to the same
height as everything else and given the right border that separates it, and
the filename centred with line-height rather than flexbox, which file inputs
do not lay out reliably.
*/
.input[type="file"] {
padding: 0 var(--control-px) 0 0;
line-height: calc(var(--control-h) - 2px);
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--ink-muted);
}
.input[type="file"]::file-selector-button {
height: calc(var(--control-h) - 2px);
margin: 0 var(--sp-3) 0 0;
padding: 0 var(--control-px);
border: 0;
border-right: 1px solid var(--border);
background: var(--surface-hover);
color: var(--ink);
font: inherit;
font-weight: 500;
cursor: pointer;
transition: background var(--transition-fast);
}
.input[type="file"]:hover::file-selector-button { background: var(--surface-active); }
.input--mono, .textarea--mono { font-family: var(--font-mono); font-size: var(--text-xs); }
.select {
appearance: none;
padding-right: var(--sp-8);
/* Chevron drawn in CSS, so no icon font and no extra element. */
background-image:
linear-gradient(45deg, transparent 50%, currentColor 50%),
linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: right 1.1rem center, right 0.85rem center;
background-size: 0.3rem 0.3rem, 0.3rem 0.3rem;
background-repeat: no-repeat;
cursor: pointer;
}
/* Sits inside a composed control that draws its own frame (the model picker). */
.select--bare {
border-color: transparent;
background-color: transparent;
width: auto;
max-width: 14rem;
padding-left: var(--sp-1);
}
.select--bare:focus { box-shadow: none; }
.checkbox {
display: flex;
align-items: center;
gap: var(--sp-2);
font-size: var(--text-sm);
cursor: pointer;
line-height: var(--leading-normal);
}
.checkbox input {
accent-color: var(--accent);
width: 1rem;
height: 1rem;
flex: none;
cursor: pointer;
}
/* Multi-column form layout, one definition. */
.grid { display: grid; gap: var(--sp-4); }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
/* --- Alerts --------------------------------------------------------------- */
.alert {
display: flex;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
border: 1px solid var(--border);
border-left-width: 3px;
border-radius: var(--radius);
font-size: var(--text-sm);
margin-bottom: var(--sp-4);
background: var(--surface);
}
.alert--error { border-left-color: var(--danger); background: var(--danger-soft); color: var(--ink); }
.alert--success { border-left-color: var(--success); background: var(--success-soft); }
.alert--warning { border-left-color: var(--warning); background: var(--warning-soft); }
.alert__icon { color: var(--danger); flex: none; margin-top: 0.15rem; }
/* --- Badges --------------------------------------------------------------- */
.badge {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
padding: 0.1rem 0.45rem;
border-radius: var(--radius-full);
font-size: var(--text-xs);
font-weight: 500;
background: var(--surface-active);
color: var(--ink-muted);
}
.badge--leaf { background: var(--leaf-soft); color: var(--leaf); }
.badge--success { background: var(--success-soft); color: var(--success); }
.badge--danger { background: var(--danger-soft); color: var(--danger); }
.badge--warning { background: var(--warning-soft); color: var(--warning); }
/* --- Application shell ----------------------------------------------------- */
.shell { display: flex; height: 100dvh; overflow: hidden; }
.sidebar {
width: var(--sidebar-width);
flex: none;
display: flex;
flex-direction: column;
background: var(--bg-sunken);
border-right: 1px solid var(--border);
min-height: 0;
}
.sidebar[hidden] { display: none; }
.sidebar__header {
display: flex;
align-items: center;
height: var(--header-height);
padding: 0 var(--sp-3);
flex: none;
}
.sidebar__brand {
display: flex;
align-items: center;
gap: var(--sp-2);
color: var(--ink);
text-decoration: none;
font-family: var(--font-display);
font-size: var(--text-lg);
font-weight: 600;
letter-spacing: 0.01em;
min-width: 0;
}
.sidebar__brand:hover { color: var(--ink); }
.sidebar__brand .brand-mark { width: 1.65rem; height: 1.65rem; flex: none; }
.sidebar__brand .brand-llm { color: var(--leaf); }
.sidebar__actions {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: 0 var(--sp-3) var(--sp-3);
flex: none;
}
.sidebar__scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0 var(--sp-2) var(--sp-3);
scrollbar-width: thin;
scrollbar-color: var(--border-strong) transparent;
}
.sidebar__footer {
flex: none;
border-top: 1px solid var(--border);
padding: var(--sp-2);
display: flex;
flex-direction: column;
gap: var(--sp-1);
}
.sidebar__tools { display: flex; align-items: center; gap: var(--sp-1); }
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
min-height: 0;
background: var(--bg);
}
/* The inspector, mirroring the sidebar on the other side of .shell. Hidden by
the `hidden` attribute, which the rule at the top of this file forces to win. */
.inspector {
width: var(--inspector-width);
flex: none;
display: flex;
flex-direction: column;
min-height: 0;
background: var(--bg-sunken);
border-left: 1px solid var(--border);
}
.inspector__header {
display: flex;
align-items: center;
gap: var(--sp-2);
height: var(--header-height);
flex: none;
padding: 0 var(--sp-3);
border-bottom: 1px solid var(--border);
}
.inspector__title {
display: flex;
align-items: center;
gap: var(--sp-2);
flex: 1;
font-size: var(--text-sm);
font-weight: 600;
color: var(--ink-muted);
}
.inspector__body {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: var(--sp-4);
scrollbar-width: thin;
scrollbar-color: var(--border-strong) transparent;
}
.inspector__heading {
display: flex;
align-items: center;
gap: var(--sp-2);
font-size: var(--text-sm);
margin: var(--sp-5) 0 var(--sp-2);
}
.inspector__note {
font-size: var(--text-xs);
color: var(--ink-muted);
line-height: var(--leading-normal);
margin: 0 0 var(--sp-3);
overflow-wrap: anywhere;
}
.inspector__facts {
display: grid;
grid-template-columns: auto 1fr;
gap: var(--sp-1) var(--sp-3);
font-size: var(--text-xs);
margin: 0;
}
.inspector__facts dt { color: var(--ink-faint); }
.inspector__facts dd { margin: 0; overflow-wrap: anywhere; }
.inspector__json {
margin: 0 0 var(--sp-3);
padding: var(--sp-2);
max-height: 22rem;
overflow: auto;
white-space: pre-wrap;
overflow-wrap: anywhere;
background: var(--code-bg);
border: 1px solid var(--code-border);
border-radius: var(--radius);
font-family: var(--font-mono);
font-size: var(--text-xs);
line-height: var(--leading-normal);
color: var(--ink-muted);
}
@media (max-width: 64rem) {
.inspector {
position: fixed;
inset: 0 0 0 auto;
z-index: 40;
box-shadow: var(--shadow-lg);
}
}
.topbar {
display: flex;
align-items: center;
gap: var(--sp-3);
height: var(--header-height);
flex: none;
padding: 0 var(--sp-4);
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.topbar__title {
font-family: var(--font-display);
font-size: var(--text-md);
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
flex: 1;
}
.topbar__actions { display: flex; align-items: center; gap: var(--sp-2); flex: none; }
/* The model picker: an avatar and a select sharing one frame, so it reads as a
single control rather than two things that happen to be adjacent. */
.model-select {
display: flex;
align-items: center;
gap: var(--sp-2);
height: var(--control-h);
padding: 0 var(--sp-1) 0 var(--sp-2);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
cursor: pointer;
transition: border-color var(--transition-fast);
}
.model-select:hover { border-color: var(--border-strong); }
.model-select:focus-within {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.model-select__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); }
/* Collapsible settings panel, shared by chat settings and anything like it. */
.panel {
flex: none;
border-bottom: 1px solid var(--border);
background: var(--bg-sunken);
max-height: 60vh;
overflow-y: auto;
}
.panel__inner {
max-width: var(--thread-max-width);
margin: 0 auto;
padding: var(--sp-5);
}
.panel__note {
color: var(--ink-muted);
font-size: var(--text-sm);
font-style: italic;
margin-bottom: var(--sp-4);
}
/* --- Sidebar navigation ---------------------------------------------------- */
.nav-group { margin-bottom: var(--sp-4); }
.nav-group:last-child { margin-bottom: 0; }
.nav-group__label {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--sp-2) var(--sp-2) var(--sp-1);
font-size: var(--text-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--ink-faint);
}
.nav-item {
display: flex;
align-items: center;
gap: var(--sp-2);
min-height: var(--control-h);
padding: 0 var(--sp-2);
border-radius: var(--radius);
color: var(--ink-muted);
text-decoration: none;
font-size: var(--text-sm);
cursor: pointer;
position: relative;
}
.nav-item:hover { background: var(--surface-hover); color: var(--ink); }
.nav-item.is-active {
background: var(--surface-active);
color: var(--ink);
font-weight: 500;
}
.nav-item__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
.nav-item__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); flex: none; }
.nav-item--model .nav-item__label { font-weight: 500; }
/* Row actions stay hidden until hover or focus, so the list reads calmly while
remaining keyboard reachable. */
.nav-item__actions {
display: flex;
align-items: center;
gap: 0.1rem;
flex: none;
opacity: 0;
transition: opacity var(--transition-fast);
}
.nav-item:hover .nav-item__actions,
.nav-item:focus-within .nav-item__actions { opacity: 1; }
.nav-empty {
padding: var(--sp-2);
margin: 0;
font-size: var(--text-xs);
color: var(--ink-faint);
font-style: italic;
}
/* --- Auth screens --------------------------------------------------------- */
.auth {
min-height: 100dvh;
display: grid;
place-items: center;
padding: var(--sp-6);
background:
radial-gradient(ellipse 60% 50% at 50% 0%, var(--leaf-soft), transparent 70%),
var(--bg);
}
.auth__card {
width: 100%;
max-width: 25rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-xl);
padding: var(--sp-8);
box-shadow: var(--shadow-lg);
}
.auth__brand { display: grid; place-items: center; gap: var(--sp-3); margin-bottom: var(--sp-6); }
.auth__brand .brand-mark { width: 3.5rem; height: 3.5rem; }
.auth__title {
font-size: var(--text-2xl);
font-family: var(--font-display);
text-align: center;
}
.auth__subtitle {
text-align: center;
color: var(--ink-muted);
font-size: var(--text-sm);
margin-top: var(--sp-2);
}
.auth__footer {
margin-top: var(--sp-5);
padding-top: var(--sp-4);
border-top: 1px solid var(--border);
text-align: center;
font-size: var(--text-sm);
color: var(--ink-muted);
}
/* --- Empty states --------------------------------------------------------- */
.empty {
flex: 1;
display: grid;
place-content: center;
justify-items: center;
gap: var(--sp-3);
padding: var(--sp-10);
text-align: center;
}
.empty__mark { width: 4.5rem; height: 4.5rem; opacity: 0.85; }
.empty__title { font-size: var(--text-xl); font-family: var(--font-display); }
.empty__text {
color: var(--ink-muted);
font-size: var(--text-sm);
max-width: 32rem;
font-style: italic;
}
/* --- Utilities ------------------------------------------------------------ */
.stack > * + * { margin-top: var(--sp-4); }
.row { display: flex; align-items: center; gap: var(--sp-3); }
.row--between { justify-content: space-between; }
.muted { color: var(--ink-muted); }
.faint { color: var(--ink-faint); }
.text-sm { font-size: var(--text-sm); }
.text-xs { font-size: var(--text-xs); }
.mono { font-family: var(--font-mono); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* --- Small screens -------------------------------------------------------- */
@media (max-width: 48rem) {
.sidebar {
position: fixed;
inset: 0 auto 0 0;
z-index: 40;
box-shadow: var(--shadow-lg);
}
/* Hiding it is the `hidden` attribute, forced to win at the top of this
file. There used to be a `[data-collapsed="true"]` rule here that nothing
ever set. */
}
/* --- Toasts ----------------------------------------------------------------
Bottom-right, stacked, above dialogs' backdrop but out of the way of the
composer.
*/
.toasts {
position: fixed;
right: var(--sp-4);
bottom: var(--sp-4);
z-index: 60;
display: flex;
flex-direction: column;
gap: var(--sp-2);
max-width: min(24rem, calc(100vw - var(--sp-8)));
pointer-events: none;
}
.toast {
display: flex;
align-items: flex-start;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: var(--radius);
background: var(--surface-raised);
box-shadow: var(--shadow-lg);
font-size: var(--text-sm);
pointer-events: auto;
opacity: 0;
transform: translateY(0.5rem);
transition: opacity var(--transition), transform var(--transition);
}
.toast.is-in { opacity: 1; transform: none; }
.toast--success { border-left-color: var(--success); }
.toast--error { border-left-color: var(--danger); }
.toast--warning { border-left-color: var(--warning); }
.toast__text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.toast__close {
flex: none;
border: 0;
background: none;
color: var(--ink-faint);
cursor: pointer;
font-size: var(--text-lg);
line-height: 1;
padding: 0 0.15rem;
}
.toast__close:hover { color: var(--ink); }
/* --- Dialogs ----------------------------------------------------------------
<dialog> gives focus trapping, Escape and page inertness for free.
*/
.dialog {
border: 1px solid var(--border);
border-radius: var(--radius-xl);
background: var(--surface);
color: var(--ink);
padding: 0;
max-width: min(28rem, calc(100vw - var(--sp-8)));
width: 100%;
box-shadow: var(--shadow-lg);
}
.dialog::backdrop { background: var(--scrim); backdrop-filter: blur(2px); }
.dialog__form { padding: var(--sp-5); display: flex; flex-direction: column; gap: var(--sp-3); }
.dialog__title { font-size: var(--text-lg); }
.dialog__message {
margin: 0;
color: var(--ink-muted);
font-size: var(--text-sm);
line-height: var(--leading-relaxed);
overflow-wrap: anywhere;
}
.dialog__actions {
display: flex;
justify-content: flex-end;
gap: var(--sp-2);
margin-top: var(--sp-2);
}
/* A destructive confirmation needs the weight of a filled button, not the
outline treatment .btn--danger gives a row action. */
.btn--danger-solid {
background: var(--danger);
border-color: var(--danger);
color: var(--ink-inverse);
}
.btn--danger-solid:hover:not(:disabled) {
background: var(--danger-hover);
border-color: var(--danger-hover);
color: var(--ink-inverse);
}
/* --- Model picker ----------------------------------------------------------
Built by hand because a <select> renders only text in an <option> -- no
avatar, no description, no badges.
*/
.picker { position: relative; }
.picker__button {
display: flex;
align-items: center;
gap: var(--sp-2);
height: var(--control-h);
max-width: 16rem;
padding: 0 var(--sp-2);
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
color: var(--ink);
font-size: var(--text-sm);
cursor: pointer;
transition: border-color var(--transition-fast);
}
.picker__button:hover { border-color: var(--border-strong); }
.picker__button[aria-expanded="true"] {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.picker__avatar { width: 1.4rem; height: 1.4rem; border-radius: var(--radius-sm); flex: none; }
.picker__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
font-weight: 500;
}
.picker__chevron { flex: none; color: var(--ink-faint); }
.picker__menu {
position: absolute;
top: calc(100% + var(--sp-1));
right: 0;
z-index: 30;
width: min(24rem, calc(100vw - var(--sp-8)));
border: 1px solid var(--border);
border-radius: var(--radius-lg);
background: var(--surface-raised);
box-shadow: var(--shadow-lg);
overflow: hidden;
}
/* The composer's attach menu opens upwards: it sits at the bottom of the
window, so a menu dropping down would be off screen. */
.picker--up .picker__menu {
top: auto;
bottom: calc(100% + var(--sp-1));
left: 0;
right: auto;
}
.picker__menu--compact { width: min(18rem, calc(100vw - var(--sp-8))); padding: var(--sp-1); }
.picker__option-note {
font-size: var(--text-xs);
color: var(--ink-faint);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* A dialog that holds a searchable list rather than a question. */
.dialog--wide { width: min(34rem, calc(100vw - var(--sp-6))); }
.dialog__results {
max-height: min(24rem, 50vh);
overflow-y: auto;
scrollbar-width: thin;
border: 1px solid var(--border);
border-radius: var(--radius);
}
.dialog__results .picker__list { max-height: none; }
.picker__search { padding: var(--sp-2); border-bottom: 1px solid var(--border); }
.input--sm { height: var(--control-h-sm); font-size: var(--text-xs); }
.picker__list { max-height: 22rem; overflow-y: auto; scrollbar-width: thin; padding: var(--sp-1); }
.picker__option {
display: flex;
align-items: flex-start;
gap: var(--sp-3);
width: 100%;
padding: var(--sp-2);
border: 0;
border-radius: var(--radius);
background: none;
color: var(--ink);
text-align: left;
cursor: pointer;
}
.picker__option:hover, .picker__option:focus-visible { background: var(--surface-hover); }
.picker__option.is-selected { background: var(--accent-soft); }
.picker__option .picker__avatar { width: 1.75rem; height: 1.75rem; margin-top: 0.1rem; }
.picker__option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.15rem; }
.picker__option-name {
display: flex;
align-items: center;
gap: var(--sp-1);
font-size: var(--text-sm);
font-weight: 500;
}
.picker__pin { color: var(--leaf); }
.picker__option-desc {
font-size: var(--text-xs);
color: var(--ink-faint);
line-height: var(--leading-normal);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.picker__option-tags { display: flex; flex-wrap: wrap; gap: var(--sp-1); }
.picker__option-tags:empty { display: none; }
.tag {
font-size: 0.6875rem;
padding: 0 0.35rem;
border-radius: var(--radius-sm);
background: var(--leaf-soft);
color: var(--leaf);
}
.picker__tick { color: var(--accent); flex: none; margin-top: 0.35rem; }
.picker__empty {
padding: var(--sp-4);
margin: 0;
text-align: center;
font-size: var(--text-sm);
color: var(--ink-faint);
}