/* Approximates the Streamlit light theme: Source Sans typeface, the #FF4B4B
   primary accent, a light-gray sidebar, rounded bordered "containers", the
   metric component, and dataframe-style tables. */

:root {
    --primary: #ff4b4b;
    /* The blue every figure draws a trial in — see plots/style.py's MARKER_COLOR. */
    --action: #636efa;
    --action-strong: #4353d8;
    --primary-hover: #ff2b2b;
    --bg: #ffffff;
    --secondary-bg: #f0f2f6;
    --text: #31333f;
    --muted: rgba(49, 51, 63, 0.6);
    --border: rgba(49, 51, 63, 0.2);
    --border-light: rgba(49, 51, 63, 0.1);
    /* One trial, one experiment, one row: the shade that means "this is the one
       you picked", shared with plots/style.py's SELECTION_COLOR so the page and the
       figures agree. Deliberately not --primary, which is the brand red and
       means "this is an action" — two different jobs that looked alike. */
    --selected: #EC8273;
    /* The same, laid under a selected row or tab. Neither changes with the
       theme: "selected" is one colour everywhere, in light and in dark. */
    --selected-tint: rgba(236, 130, 115, 0.16);
    /* A proposal: a sampled configuration nobody has run, selected in the
       trial's place. Its own colour so it never passes for a trial — plots/style.py's
       PROPOSAL_COLOR. One colour in both themes, like the selection. */
    --proposal: #8B5CF6;
    --proposal-tint: rgba(139, 92, 246, 0.12);
    /* Checked boxes and radios are a selection too. */
    accent-color: var(--selected);
    --green: #09ab3b;
    --red: #ff2b2b;
    --radius: 0.5rem;
    --rail-bg: #e4e7ec;
    /* Shades laid over a surface: a hovered row or icon, the selected tab. */
    --hover: rgba(49, 51, 63, 0.08);
    --hover-light: rgba(49, 51, 63, 0.06);
    --shadow: rgba(0, 0, 0, 0.18);
    /* Alerts: a tint behind, a darker shade of the same hue for the words. */
    --info-bg: rgba(0, 104, 201, 0.1);      --info-fg: #004280;
    --warning-bg: rgba(255, 170, 0, 0.15);  --warning-fg: #7a5200;
    --error-bg: rgba(255, 43, 43, 0.1);     --error-fg: #7d0000;
    --success-bg: rgba(9, 171, 59, 0.1);    --success-fg: #005f1e;
    --failed-fg: #b91c1c;
    /* Native controls — selects, scrollbars, number fields — drawn to match. */
    color-scheme: light;
    --font: "Source Sans Pro", "Source Sans 3", -apple-system, BlinkMacSystemFont,
            "Segoe UI", Roboto, sans-serif;
}

/* Dark, chosen under Settings → Appearance: the page carries the choice as
   `data-theme` on <html> — see `ui/context_processors.py`'s `theme`. "system"
   follows the operating system's own setting. The same values twice, because
   a media query cannot share a block with a selector. Plots take theirs from
   ui/static/ui/theme.js, which reads the same attribute. */
:root[data-theme="dark"] {
    --bg: #0e1117;
    --secondary-bg: #1b1e27;
    --text: #e6e7eb;
    --muted: rgba(230, 231, 235, 0.6);
    --border: rgba(230, 231, 235, 0.18);
    --border-light: rgba(230, 231, 235, 0.09);
    --rail-bg: #161922;
    --action: #8a92ff;
    --action-strong: #a5abff;
    --hover: rgba(230, 231, 235, 0.08);
    --hover-light: rgba(230, 231, 235, 0.05);
    --shadow: rgba(0, 0, 0, 0.55);
    --info-bg: rgba(28, 131, 225, 0.2);    --info-fg: #b9dcff;
    --warning-bg: rgba(255, 170, 0, 0.18); --warning-fg: #ffd98a;
    --error-bg: rgba(255, 43, 43, 0.2);    --error-fg: #ffbcbc;
    --success-bg: rgba(9, 171, 59, 0.2);   --success-fg: #b4f0c6;
    --failed-fg: #ff8f8f;
    color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
    :root[data-theme="system"] {
        --bg: #0e1117;
        --secondary-bg: #1b1e27;
        --text: #e6e7eb;
        --muted: rgba(230, 231, 235, 0.6);
        --border: rgba(230, 231, 235, 0.18);
        --border-light: rgba(230, 231, 235, 0.09);
        --rail-bg: #161922;
        --action: #8a92ff;
        --action-strong: #a5abff;
        --hover: rgba(230, 231, 235, 0.08);
        --hover-light: rgba(230, 231, 235, 0.05);
        --shadow: rgba(0, 0, 0, 0.55);
        --info-bg: rgba(28, 131, 225, 0.2);    --info-fg: #b9dcff;
        --warning-bg: rgba(255, 170, 0, 0.18); --warning-fg: #ffd98a;
        --error-bg: rgba(255, 43, 43, 0.2);    --error-fg: #ffbcbc;
        --success-bg: rgba(9, 171, 59, 0.2);   --success-fg: #b4f0c6;
        --failed-fg: #ff8f8f;
        color-scheme: dark;
    }
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
    font-family: var(--font);
    color: var(--text);
    background: var(--bg);
    display: flex;
    min-height: 100vh;
    line-height: 1.6;
    font-size: 16px;
}

/* ── Icon rail (outer, narrow — every top-level tab lives here) ──────────── */
nav.rail {
    width: 3.5rem;
    flex-shrink: 0;
    background: var(--rail-bg);
    border-right: 1px solid var(--border);
    /* Sticky + viewport height, independent of main.content's height: the
       flex row would otherwise stretch the rail to match a tall page (lots
       of figures/tables), centering its icons far below the visible fold. */
    position: sticky;
    top: 0;
    height: 100vh;
    z-index: 6;  /* over the sidebar, which is over the page — see nav.sidebar */
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 1rem 0;
}
nav.rail .rail-tabs { display: flex; flex-direction: column; gap: 0.25rem; }
nav.rail .rail-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius);
    font-size: 1.25rem;
    color: var(--muted);
    text-decoration: none;
}
nav.rail .rail-icon:hover { background: var(--hover); color: var(--text); }
nav.rail .rail-icon.active { background: var(--bg); color: var(--selected); box-shadow: inset 0 0 0 1px var(--border); }
/* Sign-out, above the language switcher which owns the very bottom. Rendered
   only when there is a session, so the rail is unchanged without accounts. */
nav.rail .account { position: absolute; bottom: 4rem; }
nav.rail .account button {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    font: inherit;
}

/* The optional stopping criteria, folded away: the trial count is enough for
   most runs, and four more boxes would make the common case look complicated. */
details.stopping { margin-bottom: 1rem; }
details.stopping > summary {
    cursor: pointer;
    font-size: 0.875rem;
    color: var(--muted);
    margin-bottom: 0.5rem;
}
details.stopping > summary:hover { color: var(--primary); }
details.stopping .field { margin-bottom: 0.5rem; }

.ownership { margin-top: -0.5rem; }
.ownership form { display: inline; }

/* The sign-in page, which does not use the rail/sidebar layout at all. */
body.signin { display: block; background: var(--rail-bg); }
.signin-panel { max-width: 22rem; margin: 12vh auto; padding: 0 1rem; }
.signin-panel h1 { text-align: center; margin-bottom: 1.5rem; }
.signin-panel .card { background: var(--bg); }
.signin-locale { margin-top: 1rem; display: flex; justify-content: center; align-items: center; gap: 0.4rem; }
.signin-locale .locale-icon { color: var(--muted); }
.signin-locale select { width: auto; }
.signin-panel > .caption, .signin-signout { margin-top: 0.75rem; text-align: center; }
.signin-panel .agreement { max-height: 40vh; overflow: auto; margin-bottom: 1rem; }
.signin-panel.wide { max-width: 30rem; }
.signin-panel .field label.check { display: flex; align-items: baseline; margin-bottom: 0.4rem; }
.signin-panel .legal-notice { margin-bottom: 1rem; }
.legal-text .legal-box { border-left: 3px solid var(--warning-fg); padding: 0.25rem 0 0.25rem 0.75rem; margin: 1rem 0; }
.legal-text hr.legal-divider { margin: 2.5rem 0 1rem; border: none; border-top: 1px solid var(--border); }
mark.legal-todo { background: var(--warning-bg); color: var(--warning-fg); padding: 0 0.15rem; border-radius: 2px; }
.password-rules { margin: 0.4rem 0 0; padding-left: 1.1rem; font-size: 0.8rem; line-height: 1.4; }
.signin-panel.legal { max-width: 46rem; margin-top: 6vh; }
.legal-text h2 { margin-top: 1.5rem; }
.legal-text table { border-collapse: collapse; width: 100%; }
.legal-text th, .legal-text td { border-bottom: 1px solid var(--border); padding: 0.35rem 0.5rem; text-align: left; vertical-align: top; }
.legal-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.25rem 1rem; margin-top: 2rem; font-size: 0.8rem; }
.legal-links a { color: var(--muted); }
.signin-signout button { background: none; border: 0; color: inherit; text-decoration: underline; cursor: pointer; }

/* Language switcher: a native <select> transparently overlaid on the globe
   icon, so the rail stays icon-only while the control stays fully clickable
   and its options keep showing the full language names. Pinned to the
   bottom of the rail, independent of the centered tab group above. */
nav.rail .locale {
    position: absolute;
    bottom: 1rem;
    left: 50%;
    transform: translateX(-50%);
    width: 2.5rem;
    border-radius: var(--radius);
    color: var(--muted);
}
nav.rail .locale:hover { background: var(--hover); color: var(--text); }
nav.rail .locale label {
    pointer-events: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.05rem;
    padding: 0.3rem 0;
}
nav.rail .locale .locale-icon { font-size: 1.1rem; line-height: 1; }
/* The current language code (EN/DE/ES) — the ISO abbreviation, recognizable
   to speakers of each language regardless of the UI's own language — stays
   visible at all times, not just on hover/focus. */
nav.rail .locale .locale-code {
    font-size: 0.6rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    line-height: 1;
}
nav.rail .locale select {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    border: none;
    padding: 0;
    cursor: pointer;
}
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Sidebar (inner — contents depend on the active rail tab) ───────────── */
nav.sidebar {
    width: 17rem;
    flex-shrink: 0;
    background: var(--secondary-bg);
    padding: 1.5rem 1.25rem;
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    /* Stays put while the figures scroll. Everything here is a control the
       figures are an answer to — which experiment, which metric, which trial —
       and a control that scrolls away is one you have to go and find. Sticky
       rather than fixed, the same treatment nav.rail already has, so it still
       sits in the flex row; its own contents scroll if there are more of them
       than fit. */
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    /* Above the main column. `position: sticky` makes its own stacking context,
       and without a z-index the whole sidebar paints in document order — which
       is before main.content, so the page drew over its tooltips. */
    z-index: 5;
}
nav.sidebar h1 { font-size: 1.75rem; font-weight: 700; }
nav.sidebar .actions { display: flex; gap: 0.5rem; }
/* Three to a row in a sidebar 232px wide, so tighter than a button elsewhere.
   One line each: a label that still does not fit (a long translation) ends in
   an ellipsis rather than wrapping or pushing the icon button out of the row. */
nav.sidebar .actions .btn {
    padding-inline: 0.5rem; white-space: nowrap;
    min-width: 0; overflow: hidden; text-overflow: ellipsis;
}
/* A button that is only an icon: as wide as the icon, leaving the row to the
   labelled ones beside it. */
nav.sidebar .actions .btn-icon { flex: 0 0 auto; overflow: visible; }
nav.sidebar hr { border: none; border-top: 1px solid var(--border); margin: 0.25rem 0; }

/* One decision each, stacked. The rule above a section is what separates it
   from the one before, so the first has none. */
/* flex-shrink: 0 because the sidebar is a flex column with a fixed height:
   without it the sections are squeezed to fit, and a section whose content
   clips (the selected configuration's table has `overflow: hidden`) loses rows
   off the bottom rather than overflowing visibly. The sidebar scrolls instead,
   which is what its `overflow-y` is for. */
.side-section { display: flex; flex-direction: column; gap: 0.6rem; flex-shrink: 0; }
.side-section + .side-section { border-top: 1px solid var(--border); padding-top: 1.25rem; }
.side-section > h2 {
    font-size: 0.8rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
}
.side-section .field { margin-bottom: 0; }
.side-section .metric-value { font-size: 1.75rem; }
/* What bounds the next run, folded away: it is set once, and the metric above
   it is chosen constantly. */
.side-section details.run-config > summary {
    cursor: pointer;
    font-size: 0.95rem;
    font-weight: 600;
    padding: 0.35rem 0;
    /* The one thing in the sidebar that *does* something rather than reporting
       something, so it is the one thing with a colour. */
    color: var(--action);
}
.side-section details.run-config > summary:hover { color: var(--action-strong); }
.side-section details.run-config > * + * { margin-top: 0.75rem; }
.side-section #run-status { max-width: none; }
nav.sidebar .nav-links { display: flex; flex-direction: column; gap: 0.4rem; }
nav.sidebar .nav-links a { font-size: 0.9rem; color: var(--muted); text-decoration: none; }
nav.sidebar .nav-links a:hover { color: var(--primary); }

/* Vertical tab list — sidebar navigation (experiment list, settings
   subcategories): a flat left-accent style rather than boxed buttons, shared
   by any list of same-level, mutually-exclusive destinations. */
.tab-list { display: flex; flex-direction: column; gap: 0.125rem; }
.tab-list .tab {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    border-left: 3px solid transparent;
    color: var(--text);
    text-decoration: none;
    font-size: 0.95rem;
}
.tab-list .tab:hover { background: var(--hover-light); }
/* The accent and the tint carry "selected" — in the same shade the figures use
   for it — while the label stays in the text colour. The shade is a soft coral
   chosen to sit under a scatter plot, which makes it too light to read a word
   in: as a 3:1 foreground it was failing contrast before this change and would
   have failed harder after it. Weight does the work the colour used to. */
.tab-list .tab.active {
    background: var(--selected-tint);
    border-left-color: var(--selected);
    color: var(--text);
    font-weight: 600;
}
/* The open experiment's views (Dashboard, Data Handling, Timeline): indented
   under its row and a step smaller, so they read as parts of it rather than
   as more experiments.
   The current one keeps only the accent; the tint already marks the row. */
.tab-list .experiment-views { margin: 0.125rem 0 0.375rem 0.85rem; }
.tab-list .experiment-views .tab { padding: 0.3rem 0.75rem; font-size: 0.875rem; }
.tab-list .experiment-views .tab.active { background: transparent; }

/* ── Both navs on a smaller screen ───────────────────────────────────────
   Four fifths of their size, text and all, below a 2400px window — a laptop's
   — so the figures beside them keep most of the width they were drawn for.
   Smaller text reads fine in a column of controls; a narrower column of
   full-size text would only wrap. `zoom` scales a length given in `vh` too, so
   their full-height rule is divided back out. */
@media (max-width: 2399px) {
    nav.rail, nav.sidebar { zoom: 0.8; height: calc(100vh / 0.8); }
}

/* ── Main content (Streamlit "wide" layout) ───────────────────────────── */
main.content {
    flex: 1;
    /* The sides narrow with the window, down to 1.5rem: on a laptop the
       figures need the width more than the margin does. 4rem from a 3800px
       window up. */
    padding: 3rem clamp(1.5rem, 1.7vw, 4rem) 5rem;
    max-width: 1300px;
    width: 100%;
    /* Explicitly the bottom layer, so the two sticky navs beside it can be
       given one above. */
    position: relative;
    z-index: 0;
}
main.content > * + * { margin-top: 1rem; }

/* ── Typography ────────────────────────────────────────────────────────── */
h1 { font-size: 2.25rem; font-weight: 700; }
h2 { font-size: 1.5rem; font-weight: 600; }
h3 { font-size: 1.25rem; font-weight: 600; }
.caption { color: var(--muted); font-size: 0.875rem; }
.caption strong { color: var(--text); font-weight: 600; }
code { font-family: "Source Code Pro", monospace; background: var(--secondary-bg);
       padding: 0.1rem 0.3rem; border-radius: 0.25rem; font-size: 0.9em; }

/* ── Buttons / button-links ────────────────────────────────────────────── */
button, .btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0.375rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit;
    font-weight: 400;
    text-decoration: none;
    cursor: pointer;
    transition: color 0.1s, border-color 0.1s, background 0.1s;
}
button:hover:not(:disabled), .btn:hover:not(.disabled) { border-color: var(--primary); color: var(--primary); }
button:disabled, .btn.disabled { opacity: 0.5; cursor: not-allowed; }
/* A select that cannot be used now — while a run is adding trials, or with
   nothing for it to act on — looks it, the way a disabled button does. */
select:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-primary, button[type="submit"] {
    background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600;
}
.btn-primary:hover, button[type="submit"]:hover {
    background: var(--primary-hover); border-color: var(--primary-hover); color: #fff;
}
.btn-block { width: 100%; }

/* ── Form fields (label above control, full-width) ─────────────────────── */
.field { margin-bottom: 1rem; }
.field label, form p label { display: block; font-size: 0.875rem; margin-bottom: 0.25rem; }
input[type="text"], input[type="email"], input[type="password"], input[type="number"], input[type="file"], select, form p input, form p select {
    width: 100%;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit;
}
input:focus, select:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 1px var(--primary);
}
/* A field name with an explanation behind a circled i (ui/_info.html). The row
   exists so the mark sits beside the name rather than under it. */
.field-label { display: flex; align-items: center; gap: 0.35rem; margin-bottom: 0.25rem; }
.field-label label, .field-label label.check { margin-bottom: 0; }
.info { position: relative; display: inline-flex; }
.info-mark {
    width: 1.05rem; height: 1.05rem; padding: 0; border-radius: 50%;
    border-color: var(--muted); color: var(--muted);
    font-family: Georgia, "Times New Roman", serif;
    font-size: 0.75rem; font-style: italic; font-weight: 700; line-height: 1;
    cursor: help; flex-shrink: 0;
}
.info-mark:hover:not(:disabled), .info-mark:focus-visible {
    border-color: var(--primary); color: var(--primary);
}
/* Hidden until the mark is hovered or focused. `aria-describedby` points at it
   either way, so the words are announced without it ever being opened. */
.info-bubble {
    display: none;
    position: absolute; top: calc(100% + 0.4rem); left: -0.5rem; z-index: 30;
    width: max-content; max-width: 19rem;
    padding: 0.5rem 0.65rem;
    border: 1px solid var(--border); border-radius: var(--radius);
    background: var(--bg); color: var(--text);
    box-shadow: 0 2px 10px var(--shadow);
    font-size: 0.8125rem; font-weight: 400; font-style: normal; line-height: 1.45;
    white-space: normal; text-align: left; cursor: auto;
}
.info:hover .info-bubble, .info:focus-within .info-bubble { display: block; }
/* In the sidebar the bubble is fixed rather than absolute.

   The sidebar scrolls its own contents (`overflow-y: auto`), and an absolutely
   positioned descendant is part of what that scrolls: it does not escape the
   box, it widens it — which is a horizontal scrollbar on the sidebar rather
   than a bubble over the page. No amount of `left`/`right`/`max-width` changes
   that, because the problem is not where the bubble is, it is which box it
   belongs to. Fixed positioning takes it out of the scroll container
   altogether; `position: sticky` on the sidebar does not capture it, only
   transforms and their kin would.

   Offsets left `auto` so it opens at its static position — beside the circled
   i, where an absolute bubble would have been — and `nav.sidebar`'s own
   z-index is what puts it over the main column. */
nav.sidebar .info-bubble {
    position: fixed;
    top: auto; left: auto; right: auto;
    margin-left: 0.5rem;
}
.errorlist, .form-error { color: var(--red); font-size: 0.875rem; list-style: none; }

/* ── Bordered container ("st.container(border=True)") ─────────────────── */
.card {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.25rem 1.5rem;
    background: var(--bg);
}

/* ── An experiment's timeline (ui/templates/ui/experiment_timeline.html) ──
   One line down the left, each entry a point on it: the time and how many
   trials there were, then what happened. Newest at the top. */
.timeline-heading { margin-top: 1.5rem; }
.timeline { list-style: none; padding: 0; margin: 1.5rem 0 0; max-width: 56rem; }
.timeline-entry {
    display: grid;
    grid-template-columns: 11rem minmax(0, 1fr);
    gap: 1.25rem;
    position: relative;
    padding: 0 0 1.25rem;
}
.timeline-entry::before {
    content: "";
    position: absolute;
    left: calc(11rem + 0.6rem);
    top: 0.45rem;
    bottom: -0.45rem;
    border-left: 1px solid var(--border);
}
.timeline-entry:last-child::before { display: none; }
.timeline-body { position: relative; padding-left: 1.1rem; min-width: 0; }
.timeline-body::before {
    content: "";
    position: absolute;
    left: -0.62rem;
    top: 0.42rem;
    width: 0.55rem;
    height: 0.55rem;
    border-radius: 50%;
    background: var(--bg);
    border: 2px solid var(--muted);
}
.kind-run .timeline-body::before { border-color: var(--selected); background: var(--selected); }
.kind-created .timeline-body::before, .kind-imported .timeline-body::before { border-color: var(--text); }
.timeline-when { display: flex; flex-direction: column; align-items: flex-end; text-align: right;
                 font-size: 0.85rem; font-variant-numeric: tabular-nums; }
.timeline-trials { color: var(--muted); font-size: 0.8rem; }
.timeline-summary { margin: 0; }
.timeline-children { margin: 0.35rem 0 0; padding-left: 1.1rem; font-size: 0.9rem; color: var(--muted); }
.timeline-body details { margin-top: 0.35rem; }
.timeline-body details > summary { cursor: pointer; color: var(--muted); font-size: 0.85rem; }
.timeline-details {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.2rem 1rem;
    margin: 0.5rem 0 0;
    font-size: 0.875rem;
}
.timeline-details dt { color: var(--muted); }
.timeline-details dd { margin: 0; overflow-wrap: anywhere; }
/* A value that is itself a short list — the optimizer's settings: name and
   value in two columns of their own, the names a step quieter. */
.timeline-settings {
    display: grid; grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.1rem 0.75rem; margin: 0;
}
.timeline-settings dt { color: var(--muted); font-size: 0.85rem; }
.timeline-settings dd { margin: 0; }
/* What is scheduled and has not happened: above the "Now" stop, the line
   dashed, the dot hollow and dashed, the entry outlined in dashes. */
.timeline-entry.is-scheduled::before { border-left-style: dashed; }
.is-scheduled .timeline-body::before {
    background: var(--bg); border: 2px dotted var(--muted);
    width: 0.75rem; height: 0.75rem; left: -0.72rem; top: 0.32rem;
}
.is-scheduled .timeline-when { color: var(--muted); }
.timeline-scheduled {
    border: 1px dashed var(--border);
    border-radius: 8px;
    padding: 0.5rem 0.75rem;
    margin-top: -0.5rem;
    color: var(--muted);
}
.timeline-now .timeline-when {
    text-transform: uppercase; letter-spacing: 0.06em;
    font-size: 0.75rem; font-weight: 600;
}
.timeline-now .timeline-body::before { background: var(--text); border-color: var(--text); }
@media (max-width: 700px) {
    .timeline-entry { grid-template-columns: minmax(0, 1fr); gap: 0.25rem; }
    .timeline-when { align-items: flex-start; text-align: left; }
    .timeline-entry::before { display: none; }
}

/* ── An experiment's title, and renaming it (_experiment_header.html) ── */
.experiment-title { display: flex; align-items: baseline; gap: 0.5rem; }
.rename { position: relative; flex-shrink: 0; }
.rename > summary {
    list-style: none; cursor: pointer; color: var(--muted); font-size: 1.25rem;
    padding: 0 0.25rem; border-radius: 4px;
}
.rename > summary::-webkit-details-marker { display: none; }
.rename > summary:hover, .rename[open] > summary { color: var(--text); }
/* Over the caption rather than pushing it down: a moment's edit. */
.rename-form {
    position: absolute; top: calc(100% + 0.25rem); left: 0; z-index: 10;
    display: flex; gap: 0.5rem; padding: 0.5rem;
    background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.rename-form input[type=text] { width: min(24rem, 60vw); }

/* ── An experiment before its first trial (experiment_prerun.html) ──────
   The optimizer beside the prior; one above the other when narrow. */
.prerun { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: flex-start; margin-top: 1.5rem; }
.prerun-optimizer { flex: 0 1 30rem; min-width: 0; }
.prerun-optimizer .subheader { margin-top: 0; }
.prerun-prior { flex: 1 1 32rem; min-width: 0; }

/* ── A draft's setup steps (ui/templates/ui/_setup_steps.html) ──────────
   Numbered stops joined by a line, not tabs: they are done in order. The
   current one is filled in the selected colour, a saved one carries a check,
   and one that cannot be opened yet is faded. */
.setup-steps ol {
    list-style: none; margin: 0 0 2rem; padding: 0;
    display: flex; align-items: flex-start; gap: 0; max-width: 56rem;
}
.setup-step { flex: 1; position: relative; min-width: 0; }
.setup-step:not(:last-child)::after {
    content: ""; position: absolute; top: 1.125rem; left: 2.75rem; right: 0.5rem;
    border-top: 2px solid var(--border);
}
.setup-step.is-saved:not(:last-child)::after { border-top-color: var(--selected); }
.setup-step > a, .setup-step > span {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem;
    color: var(--text); text-decoration: none; padding-right: 1rem;
}
.setup-step > a:hover .setup-step-label { text-decoration: underline; }
.setup-step-number {
    flex: none; position: relative; z-index: 1;
    width: 2.25rem; height: 2.25rem; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    font-weight: 700; font-variant-numeric: tabular-nums;
    background: var(--bg); border: 2px solid var(--border); color: var(--muted);
}
.setup-step.is-saved .setup-step-number { border-color: var(--selected); color: var(--text); }
.setup-step.is-current .setup-step-number {
    background: var(--selected); border-color: var(--selected); color: #1d1d1d;
}
.setup-step-text { display: flex; flex-direction: column; padding-top: 0.15rem; min-width: 0; }
.setup-step-label { font-weight: 600; }
.setup-step.is-current .setup-step-label { font-weight: 700; }
.setup-step-state { font-size: 0.8rem; color: var(--muted); }
.setup-step.is-locked { opacity: 0.45; }
form.new-experiment { display: contents; }
/* New and Discard draft are posts, so submit buttons, but not the page's
   primary action: drawn as the plain buttons beside them. */
form.new-experiment button.btn, .setup-heading button.btn {
    background: var(--bg); border-color: var(--border); color: var(--text); font-weight: 400;
}
form.new-experiment button.btn:hover, .setup-heading button.btn:hover {
    background: var(--bg); border-color: var(--primary); color: var(--primary);
}
.draft-badge {
    flex: none; font-size: 0.65rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: var(--muted);
    border: 1px solid var(--border); border-radius: 999px; padding: 0 0.4rem;
}
.setup-heading { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
.setup-panels { display: flex; flex-direction: column; gap: 1.5rem; max-width: 72rem; }
.setup-actions { margin-top: 1.75rem; display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
@media (max-width: 700px) {
    .setup-steps ol { flex-direction: column; gap: 0.75rem; }
    .setup-step:not(:last-child)::after { display: none; }
}

/* ── Data Handling's planned capabilities ─────────────────────────────── */
/* Its sections are the dashboard's tabs, containing panels on the same grid: what
   the tab is for above them, and a tab of controls' Save button below. */
.data-handling .data-save { margin-top: 1.25rem; align-items: center; }
.data-handling .fig-slot.shape-card > .card select { width: 100%; }

.planned-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
    gap: 1rem;
    max-width: 60rem;
}
.planned { display: flex; flex-direction: column; gap: 0.4rem; }
.planned-head { display: flex; justify-content: space-between; align-items: baseline; gap: 0.75rem; }
.planned-title { font-weight: 600; }
.planned-badge {
    flex-shrink: 0;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--muted);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 0.05rem 0.5rem;
}

/* ── Two-column analytics grid (st.columns(2)) ────────────────────────── */
.grid-2 { display: flex; gap: 1.5rem; align-items: flex-start; }
.grid-2 > .col { flex: 1; display: flex; flex-direction: column; gap: 1.5rem; min-width: 0; }
@media (max-width: 900px) { .grid-2 { flex-direction: column; } }

/* ── The experiment page's figure tabs ─────────────────────────────────────
   A bar of tabs, and under it one grid per tab. See ui/layout.py for how the
   tabs are arranged and ui/static/ui/layout.js for how they are sized. */
/* Close under the experiment's heading: it says what the tabs are of. The
   content area otherwise spaces every block 1rem apart. */
main.content > .fig-area { margin-top: 0.25rem; }
.fig-tabbar { border-bottom: 1px solid var(--border); margin: 0 0 1.5rem; }
.fig-tabs { display: flex; gap: 0.25rem; flex-wrap: wrap; }
/* The last run's summary, on the same line as what the experiment is. */
.last-run { margin-left: 1.5rem; }
.fig-tab {
    background: none; border: none; border-bottom: 2px solid transparent;
    margin-bottom: -1px; padding: 0.5rem 1rem; font: inherit; color: var(--muted);
    cursor: pointer;
}
.fig-tab:hover { color: var(--text); }
/* "Selected" as the sidebar says it: the selected colour under the tab, and the
   label in the text colour and bold — the coral is too light to read a word in
   on a light page (see `.tab-list .tab.active`). */
.fig-tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--selected); font-weight: 600; }

/* Four columns where there is room for them, two below that, one on a narrow
   window. Rows are as tall as what is in them; a figure's height comes from
   its width — see `.fig-slot`. Aligned to the top of the row, so a shorter
   figure keeps its proportions rather than stretching to a taller neighbour.

   A column is a length in rem, like the text in it, between --col-min and
   --col-max — not a share of the window, which would shrink a figure around
   text that does not shrink. --col-design is the width the figures were drawn
   for: a quarter of the row on a 4K screen at 100%. How many columns fit is
   layout.js's to decide, from the grid's own width (see `geometryOf`); the
   breakpoints below are only its estimate from the window, so the first draw,
   before it runs, is close. */
/* A tab not being shown is still laid out, off-screen at the shown one's
   width: a figure that finishes loading while its tab is in the background is
   drawn at the size it will have, where with `display: none` Plotly would be
   drawing into nothing. `inert` takes it out of focus and the tree. */
.fig-area { position: relative; }
.fig-panel.is-inactive {
    position: absolute; top: 0; left: 0; width: 100%;
    visibility: hidden; pointer-events: none;
    /* No height of its own, and what is in it clipped: laid out at full width
       all the same, but adding nothing to how far the page scrolls — that is
       the tab being shown's alone. */
    height: 0; overflow: hidden;
}
.fig-grid {
    --cols: 4;
    --gap: 1.5rem;
    --col-design: 52rem;
    --col-min: 42rem;
    --col-max: 60rem;
    display: grid;
    grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
    grid-auto-flow: row;
    align-items: start;
    gap: var(--gap);
}
@media (max-width: 3215px) { .fig-grid { --cols: 2; } }
@media (max-width: 1687px) { .fig-grid { --cols: 1; } }
.fig-empty-tab { grid-column: 1 / -1; }

/* A slot is a place on the grid; the element in it is the figure, which fills
   the slot, with its plot filling what its heading leaves.

   Its size is CSS, from its width in columns (`data-w`: 1, 2 or 4, set by the
   server and by the resize buttons) and its shape, so the page script's first
   draw already lands in a slot of the right size. Each shape keeps one
   proportion at every width:

     squat   3:2   wider than tall, about a screen tall across the whole row
     long    3:1   the same height at two columns as a squat one at one
     table   a fixed block, about a screen tall, scrolling inside
     stack   as tall as its contents

   and a figure with nothing to draw yet is as tall as its contents, too. On a
   grid narrower than four columns a slot is as wide as fits. */
.fig-slot { min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.fig-slot[data-w="1"] { grid-column: span 1; }
.fig-slot[data-w="2"] { grid-column: span 2; }
.fig-slot[data-w="4"] { grid-column: span 4; }
.fig-slot.shape-squat { aspect-ratio: 3 / 2; }
.fig-slot.shape-long { aspect-ratio: 3 / 1; }
.fig-slot.is-empty { aspect-ratio: auto; }
/* Placed by layout.js: each figure as high as it can go in the columns it
   needs, which a grid's rows cannot do. The rules above are what the page
   looks like before that — and are what makes the first draw the right size. */
.fig-grid.is-arranged { display: block; position: relative; }
.fig-grid.is-arranged > .fig-slot { position: absolute; aspect-ratio: auto; }
.fig-grid.is-arranged > .fig-empty-tab { position: static; }
/* Measuring a figure's own height: nothing inside it may scroll or clip. */
.is-measured, .is-measured .table-wrap { overflow: visible !important; flex: none !important; }
@media (max-width: 3215px) {
    .fig-slot[data-w="4"] { grid-column: span 2; }
}
@media (max-width: 1687px) {
    .fig-slot[data-w] { grid-column: span 1; }
    /* One column is the window's width, and 3:1 of that is a strip. */
    .fig-slot.shape-long { aspect-ratio: 3 / 2; }
}

.fig-slot > [data-figure] {
    flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: auto;
}
.fig-slot > [data-figure] .plot { flex: 1; min-height: 0; }
/* Sized by its contents — a figure that is several panels with controls
   between them, or any figure with nothing to draw yet. layout.js gives the
   slot as many rows as the figure's natural height needs. */
.fig-slot.shape-stack > [data-figure],
.fig-slot.shape-card > [data-figure],
.fig-slot.is-content > [data-figure] { overflow: visible; }
.fig-slot.is-content > [data-figure] .plot { flex: none; }
/* A slot's own controls, in its corners and inside the card's padding: a
   grip to drag it by (top left), a pin (top right), narrower and wider
   (bottom right). Faint until the figure is pointed at, so a page being read
   is not a page of buttons. */
.fig-slot { position: relative; }
.fig-grip, .fig-pin, .fig-size button {
    position: absolute; z-index: 2;
    width: 1.375rem; height: 1.375rem; padding: 0; line-height: 1;
    border: 1px solid transparent; border-radius: 4px;
    background: none; color: var(--muted); font-size: 0.875rem; cursor: pointer;
    opacity: 0.35; transition: opacity 0.15s;
}
.fig-slot:hover .fig-grip, .fig-slot:hover .fig-pin, .fig-slot:hover .fig-size button,
.fig-grip:focus-visible, .fig-pin:focus-visible, .fig-size button:focus-visible { opacity: 1; }
.fig-grip:hover, .fig-pin:hover, .fig-size button:hover { border-color: var(--border); background: var(--bg); }
.fig-grip { top: 2px; left: 2px; cursor: grab; }
/* The heading starts level with the grip and the pin rather than under them:
   a card here keeps only enough top padding to line its title up with them,
   and its heading row keeps clear of the two corners they sit in. The trials
   table has no card padding of its own, so its heading is inset the same way. */
.fig-slot > .card { padding-top: 0.375rem; }
.fig-slot > [data-figure] > .card-head { padding-right: 0.75rem; }
.fig-slot > [data-figure] > .subheader { padding: 0 1.75rem; }
.fig-grip:active { cursor: grabbing; }
.fig-pin { top: 2px; right: 2px; filter: grayscale(1); }
.fig-pin[aria-pressed="true"] { filter: none; opacity: 0.8; }
.fig-size { position: absolute; right: 2px; bottom: 2px; display: flex; gap: 2px; z-index: 2; }
.fig-size button { position: static; }
/* Where a dragged figure would land, and the figure being dragged. */
.fig-ghost { opacity: 0.35; }
.fig-chosen > [data-figure] { box-shadow: 0 6px 24px var(--shadow); }

/* The table is as tall as its rows. Its slot is rounded up to whole figure
   heights so what is placed under it lines up with the figures beside it; the
   rest of the slot is space below the table, not inside it. */
.fig-slot.shape-table > [data-figure] { flex: none; }

/* Three figures stacked with their controls between them. Each Plotly figure
   carries its own height, so the stack needs spacing rather than a minimum. */
.acq-stack { display: flex; flex-direction: column; gap: 0.5rem; }
.acq-panel { min-width: 0; min-height: 16rem; }
/* On the figure grid the plot fills what its slot leaves under the controls,
   as every other figure's does. Never so short that its axes have no room. */
.fig-slot .acq-stack { flex: 1; min-height: 0; }
.fig-slot .acq-panel { flex: 1; min-height: 7.5rem; }
/* The controls keep their full height however many lines they wrap to; only
   the plot gives way. Shrunk, a wrapped row's second line ran under the plot,
   which then took the clicks meant for the buttons on it. */
.fig-slot .acq-stack > :not(.acq-panel) { flex-shrink: 0; }
.acq-toggle { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; }
.acq-toggle input { width: auto; margin: 0; }
/* Wide enough for the figure grid's four columns: the content area stops
   capping its width, so the four have the room they were sized for. */
@media (min-width: 1600px) {
    main.content { max-width: none; }
}

/* ── Metric component (st.metric) ─────────────────────────────────────── */
.metric-label { font-size: 0.875rem; color: var(--muted); }
.metric-value { font-size: 2.25rem; font-weight: 600; line-height: 1.2; }
/* Which metric the number is, said quietly beside it: the score is what is
   being read, and the metric is chosen once for the whole page. */
.metric-value .metric-unit { font-size: 0.875rem; font-weight: 400; color: var(--muted); }
/* Struck through, not hidden: the search really was handed this number, and a
   reader comparing figures against the panel needs to find the same value. */
.metric-value.failed { color: var(--muted); text-decoration: line-through; text-decoration-thickness: 2px; }
.metric-delta { font-size: 0.875rem; }
.metric-delta.up { color: var(--green); }
.metric-delta.down { color: var(--red); }
/* Neither up nor down: the selected trial is the incumbent. */
.metric-delta.level { color: var(--muted); }

/* ── Dataframe-style tables ────────────────────────────────────────────── */
table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
table th, table td { padding: 0.4rem 0.75rem; border-bottom: 1px solid var(--border-light); text-align: left; }
table thead th { background: var(--secondary-bg); font-weight: 600; color: var(--muted); }
table.config td:last-child, table.trials td:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }

/* Every row picks its trial, and the selected one is the trial every figure on
   the page is showing. A tinted row rather than a border, so a table that is
   mid-sort doesn't jump by a pixel as the highlight moves. */
table.trials tbody tr { cursor: pointer; }
table.trials tbody tr:hover { background: rgba(99, 110, 250, 0.07); }
table.trials tbody tr.failed { background: rgba(255, 43, 43, 0.13); }
table.trials tbody tr.failed td:first-child { color: var(--failed-fg); }
/* After .failed, so selecting a failed trial still shows as selected — the row
   is one or the other, and which one is selected is the more urgent of the two
   to be able to see. */
table.trials tbody tr.selected { background: var(--selected-tint); }
table.trials tbody tr:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.table-wrap { border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.table-scroll { overflow-x: auto; }

/* The trials table's footer: which page, and how big a page is. Two controls
   for one question, so they share a row, with the page selector first because
   it is the one used constantly. */
.table-pager {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    margin-top: 0.6rem;
    font-size: 0.875rem;
}
.table-pager .pager-pages, .table-pager .pager-size {
    display: flex;
    align-items: center;
    gap: 0.4rem;
}
.table-pager label { margin-bottom: 0; font-size: inherit; }
.table-pager select, .table-pager input { width: auto; padding: 0.25rem 0.5rem; }
.table-pager input[type="number"] { width: 5rem; }
.table-pager .btn { padding: 0.15rem 0.6rem; line-height: 1.4; }

/* Trials table: a trial's outcome (scores, duration) is separated from the
   configuration that produced it, and the outcome columns sort on click. */
.trials .group-start { border-left: 2px solid var(--border); }
.trials th.sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.trials th.sortable::after { content: "↕"; margin-left: 0.35rem; opacity: 0.3; }
.trials th.sortable[aria-sort]::after { opacity: 1; }
.trials th.sortable[aria-sort="ascending"]::after  { content: "↑"; }
.trials th.sortable[aria-sort="descending"]::after { content: "↓"; }
.trials th.sortable:hover { color: var(--text); }

/* ── Alerts (st.info/warning/error/success) ───────────────────────────── */
/* A hidden element takes no space, whatever else claims a display. Without
   this an `.alert` toggled with the attribute still draws its own padding as a
   coloured bar with nothing in it. */
[hidden] { display: none !important; }
.alert { padding: 0.75rem 1rem; border-radius: var(--radius); font-size: 0.9rem; }
.alert.info    { background: var(--info-bg); color: var(--info-fg); }
.alert.warning { background: var(--warning-bg); color: var(--warning-fg); }
.alert.error   { background: var(--error-bg); color: var(--error-fg); }
.alert.success { background: var(--success-bg); color: var(--success-fg); }
/* The sidebar's width is the constraint: a reason and a link stack rather than
   sitting on one line, and the reason keeps its own line breaks since it is a
   message from somewhere else. */
.trial-failure { margin-top: 0.75rem; display: flex; flex-direction: column; gap: 0.4rem; }
.trial-failure .failure-reason { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.8rem; overflow-wrap: anywhere; }

/* Two fields that are one decision — a scheme and the number it needs — read
   side by side, so the number is obviously the selector's and not a setting of
   its own. */
.field-row { display: flex; gap: 1rem; align-items: flex-start; }
.field-row > .field { flex: 1; min-width: 0; }
@media (max-width: 600px) { .field-row { flex-direction: column; } }

/* An optimizer setting's accepted range and what leaving it empty would use.
   Two short facts on one line, the range first because it constrains what you
   can type and the default only says what happens if you don't. */
.field-range { display: flex; gap: 0.75rem; margin-top: 0.2rem; font-size: 0.8rem; }
.field-range .range { font-variant-numeric: tabular-nums; }
.field-range .default-hint { color: var(--muted); font-variant-numeric: tabular-nums; }

/* A figure's own pickers: one row above the plot, reading as one control for
   one figure rather than as a stack of unrelated settings. They wrap only when
   the card is genuinely too narrow to fit them side by side, and they size to
   their contents — a select stretched to the card's full width reads as a form
   field, which is what the page's other selects are and these are not. */
/* A figure's title and its pickers on one line: the pickers say what the title
   is currently showing, so they are part of the heading rather than a caption
   under it. They fall below it only when the card is too narrow to fit both. */
.card-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin-bottom: 0.5rem;
}
.card-head .subheader { margin-bottom: 0; }
.card-head .selectors { margin-bottom: 0; }
.selectors { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; margin-bottom: 0.5rem; }
.selectors select { width: auto; min-width: 0; max-width: 100%; }
/* One prior parameter: its name, an exact field, and a coarse slider. Built by
   acquisition.js from whichever distribution is chosen, so the rule has to fit
   any of them rather than a fixed set. */
/* The acquisition figure's two control rows keep their height whether or not
   anything is in them. Controls that appear and disappear move the figure under
   the reader's pointer, which is worse than a row of empty space. */
.acq-controls { min-height: 2.25rem; }
/* The prior's parameter fields belong to the row that states the shape, not to
   a row of their own: the container is only somewhere for the script to write,
   and its children are the flex items of the bar above the panel. */
.acq-prior-bar #acq-prior-params { display: contents; }
.prior-param { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; }
.prior-param input[type="number"] { width: 5.5rem; }
.prior-param input[type="range"] { width: 8rem; }

/* The importance table's magnitude columns, which are only filled once the
   selected trial's ablation has been asked for. Narrower and quieter than the
   two that are always there: they are the detail behind the share, not a
   replacement for it. */
.progress-col { font-size: 0.8125rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── Figures ────────────────────────────────────────────────────────────── */
.subheader { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; }
.plot { width: 100%; min-height: 320px; }
/* On the figure grid a plot is as tall as its slot makes it. */
.fig-slot .plot { min-height: 0; }
hr.divider { border: none; border-top: 1px solid var(--border); margin: 1.5rem 0; }

/* ── Spinner (running runs) ─────────────────────────────────────────────── */
.spinner {
    display: inline-block;
    width: 14px; height: 14px;
    border: 2px solid rgba(150, 150, 150, 0.25);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin 0.75s linear infinite;
    vertical-align: middle;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Short experiment ID shown beside the name — small and sitting on the name's
   baseline (so it aligns with the bottom of the name text). */
.exp-id {
    opacity: 0.4;
    font-size: 0.62em;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    vertical-align: baseline;
}

/* Checkbox that toggles a field between a demo source and an upload. */
.field label.check { display: inline-flex; align-items: center; gap: 0.4rem; font-weight: normal; }
.field label.check input[type="checkbox"] { width: auto; }

/* ── Breadcrumbs + back link (ui/_breadcrumbs.html) ────────────────────── */
.breadcrumbs { display: flex; align-items: baseline; gap: 1rem; flex-wrap: wrap;
               font-size: 0.875rem; margin-bottom: 0.5rem; }
.breadcrumbs ol { display: flex; gap: 0.4rem; flex-wrap: wrap; list-style: none; }
.breadcrumbs li + li::before { content: "/"; color: var(--border); margin-right: 0.4rem; }
.breadcrumbs a { color: var(--muted); text-decoration: none; }
.breadcrumbs a:hover { color: var(--primary); }
.breadcrumbs [aria-current="page"] { color: var(--text); font-weight: 600; }
.breadcrumbs .back-link { color: var(--primary); font-weight: 600; }

/* Settings pages: the per-experiment and defaults pages show the same fields,
   so they share a width. */
.settings-form { max-width: 34rem; }
.settings-form .actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }

/* Settings an experiment is inheriting rather than setting: shown so you can
   see what they are, greyed out because editing them here would do nothing. */
.pane-disabled { opacity: 0.5; }
.pane-disabled, .pane-disabled * { cursor: not-allowed; }

/* Confirmation pages (ui/_confirm_page.html). */
.confirm-message { max-width: 40rem; }
.confirm-actions { display: flex; gap: 0.5rem; margin-top: 1rem; flex-wrap: wrap; }

/* A titled list of related checkboxes (e.g. which figures to show). */
.check-group {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 1rem 1rem;
    margin-bottom: 1rem;
}
.settings-form fieldset.field { border: none; display: flex; flex-direction: column; gap: 0.4rem; }
.settings-form fieldset.field legend { margin-bottom: 0.4rem; }
.check-group legend { font-size: 0.875rem; color: var(--muted); padding: 0 0.35rem; }
.check-group label.check {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    font-weight: normal;
    padding: 0.15rem 0;
}
.check-group label.check input[type="checkbox"] { width: auto; }

/* ── A block of settings the form lays out itself ──────────────────────── */
.param-group {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.5rem 0.9rem 0.75rem;
    margin: 0 0 1rem;
}
.param-group > legend { font-size: 0.875rem; color: var(--muted); padding: 0 0.35rem; }
.param-columns { display: flex; gap: 1rem; flex-wrap: wrap; }
.param-column { flex: 1 1 11rem; min-width: 0; }
.param-column .field { margin-bottom: 0.5rem; }
.field.use-default { margin-bottom: 0.25rem; }
/* Switched off by a checkbox that is right there, so it is greyed rather than
   removed: the reader has to see what turning it back on would offer. */
.is-off { opacity: 0.45; }

/* A group's storage, on its page: how full, as a bar. */
.usage-bar { height: 0.5rem; border-radius: 999px; background: var(--border); overflow: hidden; margin: 0.25rem 0 1rem; }
.usage-bar > span { display: block; height: 100%; background: var(--selected); }

/* ── The assistant (assistant/_dock.html, assistant/_chat.html) ──────────
   Three places: the home page, where the general chat is the page; a window
   docked to the bottom right of every page, with a tab per chat, collapsed to
   a button until opened; and a browser window of its own. Only the log
   scrolls. */
.assistant { display: flex; flex-direction: column; gap: 0.75rem; min-height: 0; }
.assistant-full { height: calc(100vh - 12rem); max-width: 52rem; }
/* Before anything is said, the box to type in sits in the middle of the page;
   once the assistant answers, the conversation takes the space above it. */
.assistant-full.is-empty .assistant-log,
.assistant-full.is-empty .assistant-status { display: none; }
.assistant-full.is-empty .assistant-form { margin-block: auto; }
.assistant-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.assistant-head h2 { font-size: 1.1rem; }
.assistant-head-actions { display: flex; gap: 0.35rem; }
.assistant-head .btn { font-size: 0.8rem; padding: 0.2rem 0.6rem; }
.assistant-log { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0.5rem; min-height: 0; }
/* A full log scrolls; its messages keep their height rather than shrink to fit. */
.assistant-log > * { flex-shrink: 0; }
.assistant-msg {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    padding: 0.5rem 0.75rem;
    border-radius: var(--radius);
    max-width: 92%;
    font-size: 0.95rem;
}
.assistant-msg.user { align-self: flex-end; background: var(--selected-tint); }
.assistant-msg.assistant { align-self: flex-start; background: var(--secondary-bg); white-space: normal; }
/* An answer withdrawn for citing a paper the library lacks stays readable, marked. */
.assistant-msg.assistant.is-withdrawn { opacity: 0.55; text-decoration: line-through; }
/* A reply's Markdown (ui/markdown.js), spaced for a chat bubble. */
.assistant-msg.assistant > :first-child { margin-top: 0; }
.assistant-msg.assistant > :last-child { margin-bottom: 0; }
.assistant-msg.assistant p, .assistant-msg.assistant ul, .assistant-msg.assistant ol,
.assistant-msg.assistant pre, .assistant-msg.assistant blockquote,
.assistant-msg.assistant .md-table { margin: 0 0 0.55em; }
.assistant-msg.assistant ul, .assistant-msg.assistant ol { padding-left: 1.3em; }
.assistant-msg.assistant li + li { margin-top: 0.15em; }
.assistant-msg.assistant .md-heading { font-weight: 600; }
.assistant-msg.assistant code { font-size: 0.875em; background: var(--hover); padding: 0.05em 0.3em; border-radius: 4px; }
.assistant-msg.assistant pre { white-space: pre; overflow-x: auto; background: var(--hover); padding: 0.5em 0.65em; border-radius: var(--radius); }
.assistant-msg.assistant pre code { background: none; padding: 0; }
.assistant-msg.assistant blockquote { border-left: 3px solid var(--border); padding-left: 0.65em; color: var(--muted); }
.assistant-msg.assistant hr { border: none; border-top: 1px solid var(--border); margin: 0.6em 0; }
.assistant-msg.assistant .md-table { overflow-x: auto; }
.assistant-msg.assistant table { border-collapse: collapse; font-size: 0.875em; }
.assistant-msg.assistant th, .assistant-msg.assistant td { border: 1px solid var(--border); padding: 0.2em 0.5em; text-align: left; vertical-align: top; }
.assistant-msg.assistant th { background: var(--hover); font-weight: 600; }
.assistant-msg.notice { align-self: stretch; background: var(--warning-bg); color: var(--warning-fg); font-size: 0.875rem; }
.assistant-msg.notice.dataset { background: var(--info-bg); color: var(--info-fg); }
.assistant-tool { font-size: 0.8rem; color: var(--muted); }
.assistant-tool > summary { cursor: pointer; }
.assistant-tool pre { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 12rem; overflow-y: auto;
                      background: var(--secondary-bg); padding: 0.4rem; border-radius: var(--radius); margin-top: 0.25rem; }
.assistant-choices { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.assistant-choices > p { width: 100%; font-size: 0.95rem; }
.assistant-status { min-height: 1.2em; }
/* The composer: on the home page the dataset just over the box; then the box,
   and Send with Reset under it, together as tall as the box. */
.assistant-form { display: flex; flex-direction: column; gap: 0.4rem; }
/* The box starts at --compose-height and can be dragged taller; Send and
   Reset keep its starting height, level with its foot. */
.assistant-compose { --compose-height: 4.3rem; display: flex; gap: 0.5rem; align-items: flex-end; }
.assistant-compose textarea {
    flex: 1;
    min-width: 0;
    resize: vertical;
    height: var(--compose-height);
    min-height: var(--compose-height);
    box-sizing: border-box;
    padding: 0.5rem 0.75rem;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--text);
    font: inherit;
}
.assistant-compose textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.assistant-send { display: flex; flex-direction: column; justify-content: center; gap: 0.25rem; width: 4.25rem; height: var(--compose-height); box-sizing: border-box; flex-shrink: 0; }
.assistant-send .btn-primary { flex: none; height: calc(var(--compose-height) - 1.75rem); padding: 0.25rem 0.5rem; }
.assistant-send .assistant-reset { flex: none; height: 1.5rem; font-size: 0.75rem; padding: 0 0.4rem; }
/* Until there is something to reset, Send keeps the size it has beside Reset,
   centred where the two will stand. */
.assistant-send .assistant-reset[hidden] { display: inline-flex !important; visibility: hidden; }
.assistant-send:has(> .assistant-reset[hidden]) .btn-primary { transform: translateY(0.875rem); }
.assistant-dataset { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; font-size: 0.875rem; }
/* The upload and the demo list take the same room, so the checkbox beside
   them stays put when it switches one for the other. */
.assistant-dataset .file-pick, .assistant-dataset select { width: 20rem; max-width: 100%; height: 2.25rem; box-sizing: border-box; }
.assistant-dataset select { padding: 0.25rem 0.5rem; }
.assistant-dataset.is-missing .file-pick, .assistant-dataset.is-missing select,
.assistant-compose textarea.is-missing { border-color: var(--red); box-shadow: 0 0 0 1px var(--red); }
.assistant-missing { margin: 0; }
.assistant-dataset label.check { display: inline-flex; align-items: center; gap: 0.35rem; }
.assistant-sources { margin: 0.25rem 0 0.5rem; padding-left: 1.25rem; font-size: 0.8rem; color: var(--muted); }
.assistant-sources a { color: inherit; }
.assistant-chart { width: 100%; min-height: 220px; margin: 0.25rem 0; }
.assistant-plan {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 0.75rem 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    font-size: 0.9rem;
}
.assistant-plan h3 { font-size: 1rem; }
.assistant-plan dl { display: grid; grid-template-columns: max-content 1fr; gap: 0.15rem 0.75rem; }
.assistant-plan dt { color: var(--muted); }
.assistant-plan dd { overflow-wrap: anywhere; }
.assistant-plan ul { padding-left: 1.2rem; color: var(--warning-fg); }
.assistant-plan button { align-self: flex-start; }
.assistant dialog { border: 1px solid var(--border); border-radius: var(--radius); padding: 1.25rem;
                    max-width: 28rem; background: var(--bg); color: var(--text); margin: auto; }
.assistant dialog::backdrop { background: var(--shadow); }
.assistant dialog .selectors { display: flex; gap: 0.5rem; margin-top: 1rem; }
/* The dock: a tab on the bottom edge at the right, like a messaging site's
   chat. Collapsed, only its bar shows; open, the chat grows up from it. While
   the chat is in a window of its own, the bar says so and brings it forward. */
.assistant-dock {
    position: fixed;
    right: 1.25rem;
    bottom: 0;
    z-index: 20;
    /* Wide enough by default for the chat box's hint on one line. */
    width: var(--dock-width, min(38rem, calc(100vw - 2.5rem)));
}
.assistant-dock.collapsed { width: var(--dock-width, min(24rem, calc(100vw - 2.5rem))); }
.assistant-floating {
    position: relative;
    height: var(--dock-height, min(38rem, calc(100vh - 2rem)));
    gap: 0.6rem;
    padding: 0 0 0.75rem;
    background: var(--bg);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: var(--radius) var(--radius) 0 0;
    box-shadow: 0 0 18px var(--shadow);
    overflow: hidden;
}
.assistant-floating > :not(.assistant-head) { margin-inline: 0.85rem; }
.assistant-floating > .assistant-grip {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    width: 0.9rem;
    height: 0.9rem;
    margin: 0;
    cursor: nwse-resize;
    touch-action: none;
    background: linear-gradient(135deg, var(--muted) 0 1.5px, transparent 1.5px 4px, var(--muted) 4px 5.5px, transparent 5.5px);
    opacity: 0.5;
}
.assistant-floating > .assistant-grip:hover { opacity: 1; }
.assistant-dock.collapsed .assistant-floating { height: auto; padding: 0; }
.assistant-dock.collapsed .assistant-floating > :not(.assistant-head) { display: none; }
.assistant-floating .assistant-head {
    background: var(--secondary-bg);
    border-bottom: 1px solid var(--border-light);
    padding: 0.2rem 0.4rem 0.2rem 0;
}
.assistant-bar {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 0.5rem;
    border: none;
    background: none;
    color: var(--text);
    font: inherit;
    font-weight: 600;
    padding: 0.5rem 0.85rem;
    cursor: pointer;
    text-align: left;
    justify-content: flex-start;
}
.assistant-bar:hover:not(:disabled) { color: var(--text); border: none; }
.assistant-bar-popped { font-weight: normal; color: var(--muted); font-size: 0.85rem; display: none; }
.assistant-icon {
    border: none;
    background: none;
    color: var(--muted);
    padding: 0.2rem 0.45rem;
    font-size: 1rem;
    line-height: 1;
    border-radius: var(--radius);
}
.assistant-icon:hover:not(:disabled) { color: var(--text); background: var(--hover); border: none; }
.assistant-dock.collapsed [data-assistant-collapse] { display: none; }
.assistant-dock:not(.popped) [data-assistant-return] { display: none; }
.assistant-dock.popped [data-assistant-popout] { display: none; }
.assistant-dock.popped .assistant-bar-popped { display: inline; }
/* The dock's chats (assistant/_dock.html): a tab each, named, with the
   experiment it is about in small text under the name; only the chosen
   tab's panel shows. */
.assistant-tabs { display: flex; gap: 0.25rem; border-bottom: 1px solid var(--border-light); margin-top: -0.6rem; }
.assistant-tab {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    border: none;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    background: none;
    color: var(--muted);
    font: inherit;
    font-size: 0.85rem;
    padding: 0.35rem 0.4rem 0.3rem;
    text-align: left;
    cursor: pointer;
}
.assistant-tab:hover:not(:disabled) { color: var(--text); background: var(--hover); border: none; border-bottom: 2px solid transparent; }
.assistant-tab[aria-selected="true"], .assistant-tab[aria-selected="true"]:hover:not(:disabled) { color: var(--text); border-bottom-color: var(--primary); }
.assistant-tab-name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.assistant-tab-slot { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 0.25rem; }
.assistant-tab-slot .assistant-tab { align-self: stretch; }
/* The way back from a visit, on the visiting chat's tab. */
.assistant-tab-return { flex-shrink: 0; font-size: 0.75rem; padding: 0.15rem 0.55rem; }
.assistant-tab.is-visiting .assistant-tab-name { color: var(--info-fg); }
.assistant-caption {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 0.75rem;
    font-weight: normal;
    color: var(--muted);
}
.assistant-panel { flex: 1; gap: 0.6rem; }
/* A chat visiting from its own experiment: where from, and the way back. */
.assistant-visiting {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.4rem 0.4rem 0.7rem;
    border-radius: var(--radius);
    background: var(--info-bg);
    color: var(--info-fg);
    font-size: 0.875rem;
}
.assistant-visiting .btn-primary { flex-shrink: 0; padding: 0.2rem 0.75rem; }
.assistant-head h2 .assistant-caption { margin-left: 0.4rem; }
/* Its own window: the whole of it. */
body.assistant-window-body { display: block; height: 100vh; }
.assistant-window { height: 100vh; padding: 1rem; }
.assistant-window .assistant-head .btn { font-size: 0.8rem; padding: 0.2rem 0.6rem; }
.field-label > label.check { margin-left: 0.75rem; font-size: 0.875rem; }
/* A label with two wordings, as wide as the longer: the one not showing
   keeps its room. */
.label-swap, .field .label-swap { display: inline-grid; justify-items: start; }
.label-swap > span { grid-area: 1 / 1; }
.label-swap[data-showing="upload"] > [data-when="demo"],
.label-swap[data-showing="demo"] > [data-when="upload"] { visibility: hidden; }
/* A file chooser that says what it holds (ui/_file_pick.html). */
.file-pick {
    display: inline-flex; align-items: center; gap: 0.5rem; min-width: 0; max-width: 100%;
    padding: 0.2rem 0.4rem 0.2rem 0.2rem;
    border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg);
}
.field .file-pick { display: flex; }
/* Step 1's upload and demo list, one height, so switching moves nothing. */
.field [data-pane="dataset-demo"] select, .field [data-pane="dataset-upload"] .file-pick { height: 2.5rem; box-sizing: border-box; }
.file-pick .file-pick-button {
    display: inline-flex; margin: 0; padding: 0.2rem 0.6rem; font-size: 0.875rem;
    cursor: pointer; white-space: nowrap; flex-shrink: 0;
}
.file-pick-name { flex: 1; min-width: 0; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-pick.has-file .file-pick-name { color: var(--text); }
.file-pick-clear {
    display: none; flex-shrink: 0; width: 1.25rem; height: 1.25rem; padding: 0;
    border: none; border-radius: 50%; background: var(--red); color: #fff; font-size: 0.95rem; line-height: 1;
}
.file-pick.has-file .file-pick-clear { display: inline-flex; }
.file-pick-clear:hover:not(:disabled) { background: var(--primary-hover); color: #fff; }
.file-pick input:focus-visible ~ .file-pick-button { outline: 2px solid var(--primary); outline-offset: 2px; }

/* What the assistant filled on a page, and what it points out (assistant/act.js). */
.assistant-filled { border-left: 3px solid var(--primary); padding-left: 0.6rem; margin-left: -0.75rem; }
.assistant-highlight { outline: 3px solid var(--primary); outline-offset: 4px; border-radius: var(--radius);
    animation: assistant-pulse 1s ease-in-out 3; }
@keyframes assistant-pulse { 50% { outline-color: transparent; } }
@media (prefers-reduced-motion: reduce) { .assistant-highlight { animation: none; } }

/* Controls in columns of two rows: a choice above what modifies it — a method
   above the dimensions it is drawn in, Next Trials above Requery. A grid filled
   column by column, so every cell is as wide as its column and the two rows
   line up on both edges. A stack is only a grouping in the markup. */
.selectors.stacked {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto auto;
    gap: 0.35rem 0.5rem;
    align-items: center;
}
.selectors.stacked select { width: 100%; }
.control-stack, .control-stack[hidden] { display: contents; }
.control-row { display: flex; align-items: center; gap: 0.35rem; }
.control-row select { flex: 1; min-width: 0; }
.joiner { font-size: 0.85rem; color: var(--muted); }
/* HP Space's four pickers as two by two: the method and the first axis above
   the second and third. Hidden in the projections, which take none of them. */
#cube-axis-x { grid-area: 1 / 2; }
#cube-axis-y { grid-area: 2 / 1; }
#cube-axis-z { grid-area: 2 / 2; }
/* Held in place while hidden, so the figure under a card's head does not
   move when the controls about its curve appear. */
[data-slice-control][hidden] { visibility: hidden; }
label[data-slice-control][hidden] { display: inline-flex; }

/* A plot reaching up under its card's head (see cubeHeadroom in
   experiment_detail.html): the head stays on top and its controls take the
   pointer, the empty space between them passes it to the plot, and Plotly's
   toolbar keeps its place below the controls. */
.plot-under-head > .card-head { position: relative; z-index: 1; pointer-events: none; }
.plot-under-head > .card-head :is(.subheader, select, button, label) { pointer-events: auto; }
.plot-under-head .modebar-container { top: var(--headroom, 0) !important; }

/* The selected configuration when it is a proposal rather than a trial: a
   dashed violet frame over a faint hatch, the look of something drafted and not
   yet real, with a tag saying so above it. */
.selected-config.is-proposal {
    border: 1.5px dashed var(--proposal);
    border-radius: var(--radius);
    padding: 0.5rem 0.75rem 0.75rem;
    background: repeating-linear-gradient(135deg, var(--proposal-tint) 0 6px, transparent 6px 12px);
}
.proposal-tag {
    display: inline-block; margin-bottom: 0.35rem; padding: 0.05rem 0.5rem;
    border-radius: 999px; background: var(--proposal); color: #fff;
    font-size: 0.75rem; font-weight: 600; letter-spacing: 0.02em;
}
.run-proposal { margin: 0.6rem 0 0.25rem; }
/* In the proposal's colour rather than the brand red every other submit
   button wears: it acts on the proposal, and belongs to its frame. */
.run-proposal button[type="submit"] {
    background: var(--proposal); border-color: var(--proposal); color: #fff; font-weight: 600;
}
.run-proposal button[type="submit"]:hover:not(:disabled) {
    background: #7C3AED; border-color: #7C3AED; color: #fff;
}
/* An experiment's notes (ui/experiment_notes.html): the editor, Write and
   Preview, and the notes drawn from their Markdown. */
.notes-page { display: flex; flex-direction: column; gap: 0.75rem; }
.notes-editor { display: flex; flex-direction: column; gap: 0.5rem; }
.notes-modes { display: flex; gap: 0.25rem; border-bottom: 1px solid var(--border-light); }
.notes-mode {
    border: none; border-bottom: 2px solid transparent; border-radius: 0; background: none;
    color: var(--muted); font: inherit; font-size: 0.9rem; padding: 0.35rem 0.75rem; cursor: pointer;
}
.notes-mode:hover:not(:disabled) { color: var(--text); background: var(--hover); border: none; border-bottom: 2px solid transparent; }
.notes-mode[aria-selected="true"], .notes-mode[aria-selected="true"]:hover:not(:disabled) { color: var(--text); border-bottom-color: var(--primary); }
.notes-editor textarea {
    width: 100%; min-height: 18rem; box-sizing: border-box; padding: 0.75rem 0.9rem;
    border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); color: var(--text);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.9rem; line-height: 1.5; resize: vertical;
}
.notes-editor textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 1px var(--primary); }
.notes-rendered { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }
.notes-rendered.is-drawn, .notes-preview { white-space: normal; }
.notes-preview { min-height: 18rem; padding: 0.75rem 0.9rem; border: 1px solid var(--border-light); border-radius: var(--radius); }
.notes-rendered p, .notes-rendered ul, .notes-rendered ol, .notes-rendered pre,
.notes-rendered blockquote, .notes-rendered .md-table { margin: 0 0 0.75em; }
.notes-rendered ul, .notes-rendered ol { padding-left: 1.4em; }
.notes-rendered .md-heading { font-weight: 600; margin: 1em 0 0.4em; }
.notes-rendered .md-h1 { font-size: 1.35rem; }
.notes-rendered .md-h2 { font-size: 1.15rem; }
.notes-rendered .md-h3 { font-size: 1rem; }
.notes-rendered > :first-child { margin-top: 0; }
.notes-rendered code { font-size: 0.875em; background: var(--hover); padding: 0.05em 0.3em; border-radius: 4px; }
.notes-rendered pre { white-space: pre; overflow-x: auto; background: var(--hover); padding: 0.6em 0.75em; border-radius: var(--radius); }
.notes-rendered pre code { background: none; padding: 0; }
.notes-rendered blockquote { border-left: 3px solid var(--border); padding-left: 0.75em; color: var(--muted); }
.notes-rendered .md-table { overflow-x: auto; }
.notes-rendered table { border-collapse: collapse; font-size: 0.9em; }
.notes-rendered th, .notes-rendered td { border: 1px solid var(--border); padding: 0.25em 0.6em; text-align: left; vertical-align: top; }
.notes-rendered th { background: var(--hover); font-weight: 600; }
.notes-stale { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem 0.9rem; border: 1px solid var(--border); border-radius: var(--radius); }
