/*
 * Rubicon's own stylesheet.
 *
 * It shares nothing with css/styles.css, which is deliberate: this is a separate page and
 * borrowing the app's 292KB of styling would tie the two together at exactly the seam we
 * are keeping clean. The consequence is that the app's table and loader conventions do not
 * govern here, because none of the machinery they describe is loaded.
 *
 * The palette is the rubricator's, and the name is where it comes from: ruber, the red
 * ochre a heading was written in down the margin of a law book, and the red clay of the
 * river bed. So red is a third ink here rather than a fill. It carries the headings, the
 * verdict, the one selected row and the moment a rule is written down, and it appears
 * nowhere else. Everything under it is paper, warm grey and near-black.
 *
 * Causal Map's dark blue is gone from this page. The yellow is kept for marked text,
 * where it does a job no colour on the page can take over.
 */

/* The two families, served from rubicon/fonts/ rather than from Google, so that opening the
   page does not hand every visitor's address to a third party. Variable files, so one file
   per script covers every weight used here, and unicode-range means a browser fetches only
   the scripts the text on screen needs, which for English is the latin file alone. The
   subsets and ranges are Google's own, taken from the stylesheet it served on 10 September
   2026. Both families are under the SIL Open Font License, whose text sits beside the files.
   rubicon.html preloads the two latin files, so the browser starts on them before it has
   read this far. Do not put an @import here. */
@font-face {
    font-family: 'Newsreader'; font-style: normal; font-weight: 300 600; font-display: swap;
    src: url(../fonts/newsreader-latin.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Newsreader'; font-style: normal; font-weight: 300 600; font-display: swap;
    src: url(../fonts/newsreader-latin-ext.woff2) format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Newsreader'; font-style: normal; font-weight: 300 600; font-display: swap;
    src: url(../fonts/newsreader-vietnamese.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Newsreader'; font-style: italic; font-weight: 300 500; font-display: swap;
    src: url(../fonts/newsreader-italic-latin.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Newsreader'; font-style: italic; font-weight: 300 500; font-display: swap;
    src: url(../fonts/newsreader-italic-latin-ext.woff2) format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Newsreader'; font-style: italic; font-weight: 300 500; font-display: swap;
    src: url(../fonts/newsreader-italic-vietnamese.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Libre Franklin'; font-style: normal; font-weight: 400 600; font-display: swap;
    src: url(../fonts/libre-franklin-latin.woff2) format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Libre Franklin'; font-style: normal; font-weight: 400 600; font-display: swap;
    src: url(../fonts/libre-franklin-latin-ext.woff2) format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: 'Libre Franklin'; font-style: normal; font-weight: 400 600; font-display: swap;
    src: url(../fonts/libre-franklin-vietnamese.woff2) format('woff2');
    unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
    font-family: 'Libre Franklin'; font-style: normal; font-weight: 400 600; font-display: swap;
    src: url(../fonts/libre-franklin-cyrillic.woff2) format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: 'Libre Franklin'; font-style: normal; font-weight: 400 600; font-display: swap;
    src: url(../fonts/libre-franklin-cyrillic-ext.woff2) format('woff2');
    unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

:root {
    --rb-ink: #17130f;
    --rb-paper: #f7f4ee;
    /* A panel is a leaf of paper on the desk, half a shade lighter than the desk. */
    --rb-panel: #fdfbf6;
    --rb-wash: #f2ece2;
    --rb-line: #e4dcd0;
    --rb-quiet: #8b7f72;
    --rb-faint: #a89a8b;
    --rb-red: #8c1912;
    --rb-red-deep: #5e0f0a;
    /* Red at the weight of a wash rather than of ink: the row you are on, the panel a
       decision was written into. Never as text, which it has nowhere near the contrast for. */
    --rb-red-wash: #f6e8e4;
    --rb-bar: #ecd8d1;
    --rb-mark: #f7ed73;

    /* Run states. Red is spoken for, so failure takes it and the rest are muted enough
       to sit under it: near-neutral hues at low chroma, which is what stops a table of
       chips reading as five brands. */
    --rb-succeeded: #3f6b4a;
    --rb-skipped:   #7d7268;
    --rb-waiting:   #8a6d1f;
    --rb-running:   #445a6b;
    --rb-stale:     #6b5a63;

    /* Two families and no more. The serif reads the prose, which is most of this page;
       the sans is furniture, and is never given a sentence to carry. */
    --rb-serif: "Newsreader", Georgia, "Times New Roman", serif;
    --rb-sans: "Libre Franklin", system-ui, -apple-system, "Segoe UI", sans-serif;
}

/* The furniture: anything that labels, counts or is clicked rather than read. Written
   once, as a class, so a new control joins it by name instead of by repeating the stack. */
.rb-top, .rb-who, .rb-run-meta, .rb-group, .rb-note, .rb-status, .rb-badge,
.rb-key, .rb-legend, .rb-pills, .rb-filter, .rb-toggle, .rb-chosen, .rb-empty,
.rb-caption, .rb-pan-hint, .rb-table th, .rb-fig th, .rb-figure-over,
.rb-kind, .rb-link, .rb-drafts > summary, .rb-yaml > summary,
button, input, select, textarea, .btn {
    font-family: var(--rb-sans);
}

* { box-sizing: border-box; }

/* The page never scrolls; the panes inside it do. Laid out as two rows rather than by
   subtracting a guessed bar height from the viewport, which is what put a five-pixel
   scrollbar down the side of every screen: the bar is as tall as its own contents. */
body {
    margin: 0;
    /* `dvh` is the height a phone actually shows. `vh` is the height with the browser's
       address bar put away, so while the bar was showing the bottom of the page, the ask
       box, sat below the screen with nothing to scroll. `vh` stays as the fallback for a
       browser that does not know `dvh`. */
    height: 100vh;
    height: 100dvh;
    display: grid;
    grid-template-rows: auto 1fr;
    overflow: hidden;
    /* A serif at 14px is a serif nobody reads. The measure in every column here is short
       and the prose is the point, so the body size goes up rather than the family coming
       down to meet it. */
    font: 15px/1.6 var(--rb-serif);
    color: var(--rb-ink);
    background: var(--rb-paper);
}

/* Headings are set in the serif at a weight below bold, because a serif at 700 in a
   column 300px wide is a shout. What marks a heading here is the red, not the weight. */
h1, h2, h3, h4 { font-family: var(--rb-serif); font-weight: 500; }

/* Ruby's own headings, which are pushed two levels down so they sit inside the page's
   structure rather than competing with it. They arrive as h3 to h6 and would otherwise be
   drawn by the browser: h5 and h6 default to SMALLER than the text under them, which reads
   as a caption rather than as a heading. */
h5, h6 { font-family: var(--rb-serif); font-weight: 600; }
.rb-prose h3, .rb-said h3 { font-size: 15px; margin: 14px 0 4px; }
.rb-prose h4, .rb-said h4 { font-size: 14px; margin: 12px 0 4px; }
.rb-prose h5, .rb-said h5 { font-size: 13px; margin: 12px 0 4px; }
.rb-prose h6, .rb-said h6 {
    font-size: 12.5px;
    margin: 10px 0 4px;
    color: var(--rb-quiet);
}

/* The one heading level that is furniture rather than prose: the small red label that
   names what follows it, straight off the margin of the manuscript. */
.rb-report h3,
.rb-question-panel h3,
.rb-lightbox h3,
.rb-notes-head h3,
.rb-privacy h2 {
    font-family: var(--rb-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-red);
}

/* -- the bar ------------------------------------------------------------------ */

/* Paper rather than a dark band, ruled off in red. A dark bar is a piece of software
   announcing itself; a red rule under a name is a page. */
.rb-top {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 9px 18px;
    background: var(--rb-panel);
    color: var(--rb-ink);
    border-bottom: 2px solid var(--rb-red);
}

/* The wordmark is the way home, so it has to look pressable without becoming a button:
   a button drawn as one, beside a project picker, would read as an action on the project. */
.rb-mark {
    font-family: var(--rb-serif);
    font-size: 21px;
    font-weight: 500;
    letter-spacing: 0.005em;
    color: var(--rb-red);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
}
.rb-mark:hover { text-decoration: underline; }

/* -- home, above all the projects ------------------------------------------------ */

/* What Rubicon is, in the one sentence before the list, for somebody who has just arrived. */
.rb-home-lead { margin: 4px 0 14px; max-width: 640px; }

.rb-home-projects { width: 100%; border-collapse: collapse; margin: 6px 0 18px; }
.rb-home-projects th {
    text-align: left;
    font-family: var(--rb-sans);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--rb-quiet);
    font-weight: 600;
    padding: 4px 12px 4px 0;
    border-bottom: 1px solid var(--rb-line);
}
/* On the baseline, so the figures sit level with the project's name rather than floating
   in the middle of however many lines its description takes. */
.rb-home-projects td {
    padding: 10px 12px 10px 0;
    border-bottom: 1px solid var(--rb-line);
    vertical-align: baseline;
}
.rb-home-projects tbody tr:hover { background: var(--rb-red-wash); cursor: pointer; }
.rb-home-name { font-size: 16px; font-weight: 600; color: var(--rb-red); }
.rb-home-projects tbody tr:hover .rb-home-name { text-decoration: underline; }
/* What the project is about, in its owner's words, which is what somebody new chooses by.
   In ink rather than the quiet grey, and cut at three lines, because the field has no
   length limit and the whole of it is on the project's own hub. */
.rb-home-about {
    margin: 2px 0 0;
    max-width: 62ch;
    font-size: 14px;
    line-height: 1.5;
    color: var(--rb-ink);
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    overflow: hidden;
}
.rb-home-about.rb-home-none { color: var(--rb-faint); }
.rb-home-num, .rb-home-when { white-space: nowrap; color: var(--rb-quiet); }
.rb-home-num { text-align: right; }
.rb-home-standards { margin-top: 4px; font-family: var(--rb-sans); font-size: 11px; color: var(--rb-quiet); }
.rb-home-standards > summary { cursor: pointer; width: max-content; }
.rb-home-standards > summary:hover { color: var(--rb-red); }
.rb-home-standards > p { margin: 2px 0 0; max-width: 62ch; }

/* Where the canvas is too narrow for five columns, which is a phone and also a laptop with
   the other two columns open, each project becomes a block: its name and what it is about,
   then its figures on one line under them, each saying what it is since the headings have
   gone. Measured against the canvas rather than the window, for the reason `.rb-graph`
   gives. */
@container canvas (max-width: 660px) {
    .rb-home-projects thead { display: none; }
    .rb-home-projects, .rb-home-projects tbody, .rb-home-projects tr { display: block; }
    .rb-home-projects tr { padding: 10px 0; border-bottom: 1px solid var(--rb-line); }
    .rb-home-projects td { display: inline; padding: 0; border: 0; }
    .rb-home-projects td.rb-home-project { display: block; margin-bottom: 5px; }
    .rb-home-projects td[data-label] {
        display: inline-block;
        margin-right: 14px;
        font-family: var(--rb-sans);
        font-size: 12px;
        text-align: left;
    }
    .rb-home-projects td[data-label]::before { content: attr(data-label) ": "; }
    .rb-home-projects tr.rb-row-atwork { background: var(--rb-wash); }
}
.rb-project { position: relative; width: auto; min-width: 260px; }
.rb-who { margin-left: auto; color: var(--rb-faint); font-size: 12px; }

/* The project picker: a text box with the matches under it. The behaviour is shared with
   the app (js/combo-picker.js); this is the page's own paper-and-red reading of it, since
   nothing here loads the app's stylesheet. */
.rb-project-list {
    position: absolute;
    top: calc(100% + 3px);
    left: 0;
    right: 0;
    z-index: 40;
    max-height: 340px;
    overflow-y: auto;
    background: var(--rb-panel);
    border: 1px solid var(--rb-line);
    border-radius: 3px;
    box-shadow: 0 6px 20px rgba(23, 19, 15, 0.14);
}
.rb-project-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 10px;
    font-family: var(--rb-sans);
    font-size: 13px;
    color: var(--rb-ink);
    cursor: pointer;
}
.rb-project-option:hover { background: var(--rb-wash); }
/* Red as the wash it is elsewhere on this page: the row you are on. */
.rb-project-option.rb-on { background: var(--rb-red-wash); }
/* A project with a run working in it, pulsing in the same red as the bar across the top of
   the page. The bar says that something somewhere is running and says nothing about where,
   so this is the answer to it: open the list and the working project is the one moving.
   Steve, 9 September 2026. Drawn as an inset ring rather than a border, so a row that
   starts or stops working does not change size and shove the list under the pointer. */
@keyframes rb-project-pulse {
    0%, 100% { box-shadow: inset 0 0 0 2px var(--rb-red); }
    50%      { box-shadow: inset 0 0 0 2px transparent; }
}
.rb-project-option.rb-project-atwork { animation: rb-project-pulse 1.6s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
    /* Still marked, because the mark is the whole point; it just stops moving. */
    .rb-project-option.rb-project-atwork {
        animation: none;
        box-shadow: inset 0 0 0 2px var(--rb-red);
    }
}
.rb-project-option-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A project of yours Rubicon has not worked on yet, found by typing its name. Quieter, so a
   list of matches still says which of them hold work. */
.rb-project-option.rb-project-fresh .rb-project-option-name { color: var(--rb-quiet); }
/* Matching is loose enough that the letters can be scattered through a name, so a hit
   would otherwise look arbitrary. */
.rb-project-match { color: var(--rb-red); font-weight: 600; }
.rb-project-empty {
    padding: 6px 10px;
    font-family: var(--rb-sans);
    font-size: 12px;
    color: var(--rb-quiet);
}

/* -- controls ------------------------------------------------------------------ */

/* Bootstrap's dark and outline-dark are what the markup asks for, and this page wants
   them in its own two inks. Overridden here rather than by renaming classes across the
   markup, so the page keeps working while the styling is still being argued about.

   Red is the primary action, which is the one place on this page red means go rather
   than take care. That is the reading the app wants: pressing the button is crossing. */
.rb-top .form-select,
.rb-top .form-control,
.rb-filter .form-control,
.rb-newq-form .form-control,
.rb-ask textarea {
    border-color: var(--rb-line);
    background-color: var(--rb-panel);
    color: var(--rb-ink);
    border-radius: 3px;
}
.rb-top .form-select:focus,
.rb-top .form-control:focus,
.rb-filter .form-control:focus,
.rb-newq-form .form-control:focus,
.rb-ask textarea:focus {
    border-color: var(--rb-red);
    box-shadow: 0 0 0 2px var(--rb-red-wash);
}

.btn-dark {
    background-color: var(--rb-red);
    border-color: var(--rb-red);
    border-radius: 3px;
    font-weight: 500;
    letter-spacing: 0.02em;
}
.btn-dark:hover, .btn-dark:focus, .btn-dark:active {
    background-color: var(--rb-red-deep);
    border-color: var(--rb-red-deep);
}

.btn-outline-dark, .btn-outline-secondary {
    color: var(--rb-quiet);
    border-color: var(--rb-line);
    background-color: var(--rb-panel);
    border-radius: 3px;
}
.btn-outline-dark:hover, .btn-outline-secondary:hover {
    color: var(--rb-red);
    border-color: var(--rb-red);
    background-color: var(--rb-red-wash);
}

/* -- the two halves ----------------------------------------------------------- */

/* Conversation, canvas, runs. The conversation is first because it is the way in. */
/* The two side columns are variables so the dividers can move them, and the middle is
   whatever is left. A person reading a long coding instruction wants the canvas wide; a
   person arguing with Ruby wants her wide; neither wants to be told which. */
/* Questions, then the conversation, then the canvas, which takes what is left.
   Steve, 3 September 2026: "chat should be second column, with wider output canvas moved
   from 2 to 3 position." The canvas is where the work ends up, and a coding instruction,
   a workflow read as a document and a table of quotes all want the room; the conversation
   is a column of short paragraphs and never did. */
.rb-main {
    display: grid;
    grid-template-columns: var(--rb-left, 300px) 5px var(--rb-mid, 400px) 5px minmax(0, 1fr);
    min-height: 0;
}

@media (max-width: 1200px) {
    .rb-main { --rb-left: 260px; --rb-mid: 340px; }
}



.rb-drag {
    cursor: col-resize;
    background: var(--rb-line);
    /* The grab area is wider than the line, because a five-pixel target is a five-pixel
       target however it is drawn. */
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    background-clip: padding-box;
    box-sizing: content-box;
    width: 1px;
    margin: 0 -4px;
    z-index: 2;
}
.rb-drag:hover, .rb-drag.rb-dragging { background-color: var(--rb-ink); }
.rb-drag:focus-visible { outline: 2px solid var(--rb-ink); outline-offset: -2px; }

.rb-side {
    display: grid;
    /* New question, its form, the filter, the chosen bar, Project home, the list. */
    grid-template-rows: auto auto auto auto auto 1fr;
    min-height: 0;
    background: var(--rb-panel);
}

.rb-nav { padding: 10px 12px 0; display: grid; gap: 6px; }

/* Project home carries `.rb-run` in the markup, so its size, padding, rule, hover and
   selected state all come from the question rows and cannot drift from them. What is left
   here is only what makes it the one row that is not a question: the house, and a top rule
   so the list still starts with a line under the filter. "+ New question" stays a plain
   button, since it starts something rather than opening it. */
.rb-home-row { border-top: 1px solid var(--rb-line); }
.rb-home-row .rb-run-name { font-weight: 500; padding-right: 16px; }
.rb-home-row .fa-house { color: var(--rb-red); margin-right: 6px; font-size: 12px; }

.rb-newq-form {
    padding: 10px 12px;
    display: grid;
    gap: 8px;
    border-bottom: 1px solid var(--rb-line);
}

.rb-newq-form[hidden] { display: none; }

/* -- a report section ---------------------------------------------------------- */

/* Read like a document rather than like a screen: one column, generous measure, and the
   finding before the machinery. */
.rb-report {
    max-width: 720px;
    padding: 26px 30px 60px;
    line-height: 1.55;
}

.rb-report h1 { font-size: 30px; font-weight: 400; line-height: 1.22; margin: 0 0 6px; letter-spacing: -0.005em; }
.rb-report h2 { font-size: 20px; margin: 30px 0 8px; }
/* Size and colour come from the one red-label rule at the top of this file. Only the
   spacing is local, because a rule that restates them is a rule that drifts from them. */
.rb-report h3 { margin: 22px 0 6px; }
.rb-report p { margin: 0 0 10px; }
.rb-report ul { margin: 0 0 10px; padding-left: 20px; }
.rb-report footer { margin-top: 34px; padding-top: 12px; border-top: 1px solid var(--rb-line); }

/* The verdict is the thing an evaluation is asked for, so it is set as the answer rather
   than as a field: large, in the serif, in the red the page has kept for it, ruled off
   above the way a heading in a law book was. */
.rb-verdict {
    font-family: var(--rb-serif);
    font-size: 38px;
    font-weight: 400;
    line-height: 1.1;
    color: var(--rb-red);
    border-top: 2px solid var(--rb-red);
    border-bottom: 1px solid var(--rb-line);
    padding: 10px 0 12px;
    margin: 4px 0 18px;
}

.rb-instruction {
    margin: 0 0 10px;
    padding: 8px 12px;
    border-left: 3px solid var(--rb-line);
    color: var(--rb-ink);
    white-space: pre-wrap;
    font-size: 13px;
}

@media print {
    .rb-top, .rb-chat, .rb-side, .rb-caption, .rb-drag { display: none !important; }
    .rb-main { display: block; height: auto; }
    .rb-graph { overflow: visible; height: auto; }
    .rb-report { max-width: none; padding: 0; }

    /* The page never scrolls and the panes inside it do, which is right on a screen and
       is a single-page document on paper: a body one viewport tall with its overflow
       hidden throws away everything below the fold. It matters twice over, because this
       stylesheet is also what the server typesets a report with, and WeasyPrint reads
       print rules. Without this the PDF came out one page long. */
    body { height: auto; display: block; overflow: visible; }
}

/* Choosing rows, and the bar that appears when any are chosen.

   Archiving one thing should cost one click, so the button is on the row and appears on
   hover. Archiving fifteen is a different job, and doing it one hover at a time is what
   makes somebody give up and leave the list cluttered, so the checkboxes are there when
   they are wanted and out of the way when they are not. */
.rb-row { position: relative; display: flex; align-items: stretch; }
.rb-row > .rb-run { flex: 1; min-width: 0; }

.rb-pick {
    flex: 0 0 auto;
    margin: 0 2px 0 8px;
    align-self: center;
    opacity: 0;
    transition: opacity 0.12s;
    accent-color: var(--rb-red);
}
.rb-row:hover .rb-pick,
.rb-pick:checked,
.rb-pick:focus-visible { opacity: 1; }

.rb-arch {
    flex: 0 0 auto;
    border: 0;
    background: none;
    color: var(--rb-quiet);
    padding: 0 10px;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s;
}
.rb-row:hover .rb-arch,
.rb-arch:focus-visible { opacity: 1; }
.rb-arch:hover { color: var(--rb-ink); }
.rb-archived { opacity: 0.55; }
.rb-archived .rb-arch { opacity: 1; }

.rb-chosen {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 8px 12px;
    background: var(--rb-ink);
    color: var(--rb-panel);
    font-size: 12px;
}
.rb-chosen .rb-link { color: var(--rb-mark); }

/* Select all or none. It arrives with the bar, so it exists only once something has been
   chosen, which is the point at which choosing the rest becomes a job worth one click.
   Indeterminate while the choice is partial, and the browser draws that itself. */
.rb-pick-all {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin: 0;
    cursor: pointer;
}
.rb-pick-all input { accent-color: var(--rb-red); margin: 0; }

.rb-filter {
    padding: 8px 12px;
    border-bottom: 1px solid var(--rb-line);
    display: flex;
    align-items: center;
    gap: 10px;
}

.rb-filter input[type="search"] { flex: 1; min-width: 0; }

.rb-toggle {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--rb-quiet);
    white-space: nowrap;
    margin: 0;
    cursor: pointer;
}

.rb-runs {
    overflow-y: auto;
    min-height: 0;
    background: var(--rb-panel);
}

/* The archive control sits over the row rather than inside it, because a button inside a
   button is not valid HTML and the row itself is the thing you click to open a run. */
.rb-row { position: relative; }
.rb-row:hover .rb-arch, .rb-arch:focus { opacity: 1; }
.rb-archived .rb-run-name { color: var(--rb-quiet); font-weight: 500; }

.rb-arch {
    position: absolute;
    top: 8px;
    right: 8px;
    border: 0;
    background: var(--rb-panel);
    border-radius: 3px;
    padding: 3px 6px;
    color: var(--rb-quiet);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s;
}

.rb-arch:hover { color: var(--rb-ink); }
.rb-arch[data-on="true"] { opacity: 1; }
.rb-hidden-note { padding: 10px 16px; }

/* `min-height: 0` on both axes, not just the width. Without it a grid item's automatic
   minimum is its content, so a long question panel in the middle column stretched the
   whole row to its own height, pushed the chat column's Ask button to y=1254 in an 849px
   viewport, and the primary action was simply off the screen with nothing to scroll. */
.rb-canvas { display: flex; flex-direction: column; min-width: 0; min-height: 0;
             position: relative; }

/* The canvas alone, with the questions and the chat put away rather than narrowed: a
   table one column wider than the room is unreadable at any narrowing, and the dividers
   cannot give it enough. The button sits over the caption's right end, so the caption
   keeps room for it whatever it says. */
.rb-wide {
    position: absolute;
    top: 6px;
    right: 10px;
    z-index: 2;
    border: 1px solid transparent;
    border-radius: 4px;
    background: none;
    color: var(--rb-quiet);
    font-size: 14px;
    line-height: 1;
    padding: 6px 8px;
    cursor: pointer;
}
.rb-wide:hover { color: var(--rb-ink); border-color: var(--rb-line); }
.rb-wide[aria-pressed="true"] { color: var(--rb-ink); }
body.rb-wide-on .rb-main { grid-template-columns: minmax(0, 1fr); }
body.rb-wide-on .rb-side,
body.rb-wide-on .rb-chat,
body.rb-wide-on .rb-drag { display: none; }
/* The pane holds a cytoscape canvas, which sizes itself and never overflows, or a paper
   document, which does. So it scrolls. */
/* The canvas is what the document and its map have to fit into, and how wide it is has
   nothing to do with how wide the window is: the two columns beside it can be open or put
   away. So it is a query container, and the rules below ask it rather than the viewport.
   Keyed to the window, a map was dropped on a 1100px screen with the canvas holding the
   whole of it, and kept on a 1600px screen where the canvas was 700px. */
.rb-graph {
    flex: 1;
    min-height: 0;
    overflow: auto;
    position: relative;
    container-type: inline-size;
    container-name: canvas;
}

.rb-pan-hint {
    position: absolute;
    left: 12px;
    bottom: 10px;
    font-size: 11px;
    color: var(--rb-quiet);
    background: rgba(253, 251, 246, 0.9);
    padding: 2px 6px;
    border-radius: 3px;
    pointer-events: none;
}

.rb-caption {
    border-bottom: 1px solid var(--rb-line);
    /* Room at the right end for the widen button, which is drawn over this row. */
    padding: 10px 46px 10px 18px;
    background: var(--rb-panel);
    display: flex;
    align-items: baseline;
    gap: 14px;
    flex-wrap: wrap;
}

.rb-question { color: var(--rb-quiet); font-style: italic; }
.rb-back { font-size: 13px; margin-right: 4px; }

/* -- runs --------------------------------------------------------------------- */

.rb-run {
    display: block;
    width: 100%;
    text-align: left;
    padding: 10px 16px 10px 24px;
    border: 0;
    border-bottom: 1px solid var(--rb-line);
    background: none;
    cursor: pointer;
    position: relative;
}

.rb-run:hover { background: var(--rb-wash); }
.rb-run-name { display: block; font-weight: 600; }
.rb-run-meta { display: block; color: var(--rb-quiet); font-size: 12px; }

/* -- the one selected-state look, shared by Project Home and every question row --
   Steve, 3 September 2026: the two did not read as the same kind of control, and the
   selected state itself was too faint to spot at a glance. Both get the same darker fill,
   the same left bar, and a chevron that is reserved space rather than added on select, so
   picking a row does not nudge its text sideways.

   Written against BOTH carriers on purpose. `rb-on` lands on the wrapping row for a
   question (from the list markup and from opening one) and on the button itself for a
   loose run (from markSelected), so a rule naming only one of them styles half the list
   and looks, from the outside, exactly like a rule that did not load. */
.rb-run.rb-on,
.rb-row.rb-on > .rb-run {
    background: var(--rb-red-wash);
    box-shadow: inset 4px 0 0 var(--rb-red);
}
.rb-run.rb-on .rb-run-name,
.rb-row.rb-on > .rb-run .rb-run-name { font-weight: 700; }
.rb-run::before {
    content: "\276F";
    position: absolute;
    left: 8px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 11px;
    color: var(--rb-red);
    visibility: hidden;
}
.rb-run.rb-on::before,
.rb-row.rb-on > .rb-run::before { visibility: visible; }

.rb-status {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 2px;
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--rb-panel);
    margin-right: 4px;
}

/* Named with the chip class as well, so a status beats the fallback by specificity
   rather than by being written above it. It was written above it, and the fallback
   `.rb-status` a few rules down therefore won every one of these: succeeded, failed,
   waiting, running, skipped and stale all painted the same grey, so a table of
   twenty-three attempts said nothing about which had failed. `.rb-status.rb-not-run`
   below was already written this way, which is the tell: the author knew the shape and
   applied it to one case. A default that depends on source order to stay a default is
   the same fault the phone media query had this morning. */
.rb-status.rb-succeeded { background: var(--rb-succeeded); }
.rb-status.rb-skipped   { background: var(--rb-skipped); }
.rb-status.rb-waiting   { background: var(--rb-waiting); }
.rb-status.rb-failed    { background: var(--rb-red); }
.rb-status.rb-running   { background: var(--rb-running); }
/* A run whose process went silent. Not a status the ledger stores: it is derived
   from the heartbeat, so the record still says what it honestly recorded. */
.rb-status.rb-stale     { background: var(--rb-stale); }
/* The same colours where the class is used away from a chip. Tokens rather than repeated
   hexes, because the chip and the legend swatch are the same claim about the same state
   and drifted apart once already. */
.rb-succeeded { background: var(--rb-succeeded); }
.rb-skipped   { background: var(--rb-skipped); }
.rb-waiting   { background: var(--rb-waiting); }
.rb-failed    { background: var(--rb-red); }
.rb-running   { background: var(--rb-running); }
.rb-stale     { background: var(--rb-stale); }

/* -- a theory of change, with what the evidence did to it --------------------- */

/* Tabs rather than a row of small links. Steve, 4 September 2026: "this could be 2 tabs:
   the result canvas, the workflow." They were three underlined links in a caption, which
   reads as somewhere to click for more detail rather than as the choice of what the
   canvas is showing, and the one that matters most was not among them. */
.rb-views { margin-left: auto; display: flex; gap: 2px; align-self: flex-end;
    margin-bottom: -11px; }
.rb-views .rb-link {
    padding: 4px 12px;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    text-decoration: none;
    color: var(--rb-quiet);
}
.rb-views .rb-link:hover { color: var(--rb-ink); background: var(--rb-red-wash); }
.rb-views .rb-on {
    color: var(--rb-ink);
    font-weight: 600;
    background: var(--rb-page);
    border-color: var(--rb-line);
}

/* Each result under its own heading, with air between them: a run that produced a table
   and a judgement is two answers, not one long page. */
.rb-result + .rb-result { margin-top: 30px; padding-top: 22px; border-top: 1px solid var(--rb-line); }

/* An asset the Result tab names rather than reads out. A coding pass makes thousands of
   quotes and printing those is not a reading of them. */
.rb-result-aside h2 { font-size: 17px; }

/* The chain, under each result's heading. It is what makes the list readable without the
   diagram beside it: a table above a verdict says nothing until it says the verdict read
   it. */
.rb-came-from { margin: -2px 0 10px; }

/* What is being worked on, above the results, while there is work being done. */
.rb-running-now { border-left: 3px solid var(--rb-red); padding-left: 14px; }

/* The theory of change, drawn inside the asset it is. Cytoscape draws into whatever box it
   is given and a box with no height is a box it draws nothing in, so this one says how
   tall it is. It is its own container rather than the lightbox body for a second reason:
   cytoscape binds mousedown to its container and prevents the default, so a graph drawn
   into the pane that also holds the prose takes selection away from the words. */
.rb-asset-graph {
    position: relative;
    height: min(72vh, 760px);
    margin: 4px 0 14px;
    border: 1px solid var(--rb-line, #e3ddd3);
    border-radius: 4px;
    background: var(--rb-wash, #f7f4ee);
}

/* The key sits on the drawing rather than under it, so a tall graph does not push it off
   the bottom of the box. */
.rb-asset-graph .rb-legend {
    position: absolute;
    left: 10px;
    right: 10px;
    bottom: 8px;
    background: rgba(255, 255, 255, 0.92);
    border-radius: 4px;
    padding: 6px 8px;
}

/* Where you are, in the app's own words, at the head of the canvas. The three places a
   person can be are the project hub, a question hub and a workflow, and the strip that
   carries the tabs is where it says which. */
.rb-where {
    font-family: var(--rb-sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-red);
}

/* A row that opens one thing is clickable across its width, and has to look it.
   Steve, 7 September 2026: "it is not obvious to click on a workflow, make each row more
   obviously clickable." A pointer and a pale wash on hover were all it had, and neither
   appears until the pointer is already on the row, so nothing on a settled screen said the
   rows were controls at all.

   So the affordance is at rest as well as under the pointer: the name in the first cell is
   drawn in the page's red, the colour everything you can press is drawn in here. On hover
   the whole row takes the red wash and a red edge down its left, and the name underlines,
   which is the link idiom and needs no learning. The row's own controls sit in the last
   cell and keep their own jobs, so the edge marks the row without turning it into one
   giant button.

   `:focus-within` rather than a tabindex on the row. The first cell already holds a
   button, which is the keyboard's way in; a second tab stop for the same act would make
   every table twice as long to walk through. Lighting the row when that button takes focus
   puts the keyboard and the pointer on the same footing.

   Every rule is behind `:has([data-open-run], [data-open-draft])`, which is the same
   predicate `wirePanel` uses to decide what a row click opens. Without it the pointer and
   the wash were on every `.rb-table` in the app, evidence tables and criteria tables
   included, none of which opens anything: a row that says press me and does nothing is
   worse than one that says nothing at all. */
.rb-table tbody tr:has([data-open-run], [data-open-draft]) {
    cursor: pointer;
    transition: background-color 90ms ease;
}
.rb-table tbody tr:has([data-open-run], [data-open-draft]) > td:first-child {
    box-shadow: inset 3px 0 0 transparent;
}
.rb-table tbody tr:has([data-open-run], [data-open-draft]):hover,
.rb-table tbody tr:has([data-open-run], [data-open-draft]):focus-within {
    background: var(--rb-red-wash);
}
.rb-table tbody tr:has([data-open-run], [data-open-draft]):hover > td:first-child,
.rb-table tbody tr:has([data-open-run], [data-open-draft]):focus-within > td:first-child {
    box-shadow: inset 3px 0 0 var(--rb-red);
}
.rb-table tbody tr:has([data-open-run], [data-open-draft]) .rb-run-name {
    color: var(--rb-red);
}
.rb-table tbody tr:has([data-open-run], [data-open-draft]):hover .rb-run-name,
.rb-table tbody tr:has([data-open-run], [data-open-draft]):focus-within .rb-run-name {
    text-decoration: underline;
}
/* An archived row is put away, so it must not read as the liveliest thing on the page. */
.rb-table tbody tr.rb-archived .rb-run-name { color: var(--rb-quiet); }

.rb-elevate-picks { display: flex; flex-wrap: wrap; gap: 8px; }

/* Its own tab now, so it takes the pane. The pane scrolls and cytoscape does not, so the
   diagram gets a height it can be measured against rather than the pane's own. */
.rb-map-pane { height: calc(100vh - 190px); min-height: 340px; }

/* The diagram's key sits under it rather than over it, because the diagram can be as tall
   as it likes here and there is no corner to tuck into. Named twice to beat `.rb-legend`,
   which is absolute and is declared after this: at equal specificity the later rule wins,
   and the key floated up the page and landed across the Accept button. */
.rb-legend.rb-diagram-key {
    position: static;
    flex-wrap: wrap;
    gap: 6px 16px;
    margin: 10px 22px 24px;
}

/* A status nothing else names. The chip's white text used to land on nothing. */
.rb-status { background: var(--rb-quiet); }
.rb-status.rb-not-run { background: var(--rb-faint); }

/* A workflow's states, which are not a run's. Without these the three that matter most
   were the same grey: candidate and accepted are the whole point of listing workflows,
   and archived has to read as put away rather than as another kind of result. */
.rb-status.rb-candidate { background: var(--rb-succeeded); }
.rb-status.rb-accepted  { background: var(--rb-red); }
.rb-status.rb-archived  { background: var(--rb-faint); }
/* Somebody stopped it. Not a failure, and it must not read as one. */
.rb-status.rb-cancelled { background: var(--rb-skipped); }
/* Somebody stopped it. Not a failure, and it must not read as one. */
.rb-status.rb-cancelled { background: var(--rb-skipped); }

.rb-legend {
    position: absolute;
    left: 12px;
    right: 12px;
    bottom: 0;
    display: flex;
    align-items: center;
    gap: 14px;
    flex-wrap: wrap;
    max-width: calc(100% - 24px);
    background: rgba(253, 251, 246, 0.9);
    padding: 4px 8px;
    border-radius: 3px;
}

.rb-key { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.rb-key i { width: 14px; height: 3px; border-radius: 2px; display: inline-block; }

.rb-theory-supported          { background: var(--rb-succeeded); }
.rb-theory-contradicted       { background: var(--rb-red); }
.rb-theory-contested          { background: var(--rb-waiting); }
.rb-theory-nobody-addressed-it,
.rb-theory-nothing-decisive   { background: var(--rb-skipped); }

/* -- questions ---------------------------------------------------------------- */

/* A question is the unit of work, so its row is taller than a run's and its text is not
   truncated: the whole question is the thing you are choosing between. */
/* Room at the top right for the archive control, which sits over the row rather than in
   it. The title ran under the bin icon, and got worse when the titles went to serif. */
.rb-q .rb-run-name { white-space: normal; line-height: 1.35; padding-right: 26px; }
.rb-starred { color: #b8860b; }

/* The standing conversation is the way in rather than one question among many. */
.rb-general { background: var(--rb-wash); }
.rb-general .rb-run-name { font-style: italic; color: var(--rb-quiet); font-weight: 400; }

.rb-group {
    padding: 12px 16px 4px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-red);
    border-top: 1px solid var(--rb-line);
}

/* Two words, because there are two states and neither is set by hand: a question with an
   accepted workflow behind it is answered and every other one is not. */
.rb-q-unanswered { background: var(--rb-running); }
.rb-q-answered   { background: var(--rb-succeeded); }

.rb-question-panel { max-width: 760px; }
.rb-question-panel h2 { font-size: 24px; font-weight: 400; line-height: 1.28; }
.rb-question-panel h2:focus { outline: 2px solid var(--rb-line); outline-offset: 4px; }
.rb-question-panel h3 { margin: 22px 0 6px; }
.rb-question-panel textarea { max-width: 640px; }

/* The accepted answer, which used to sit under a heading reading "The answer". The
   heading said what the red rule beside it already says, so it went and the block carries
   its own space now that no heading is carrying it. */
.rb-answer {
    border-left: 3px solid var(--rb-red);
    padding: 4px 0 4px 12px;
    margin: 18px 0;
}

/* Elevating sits at the foot of a result, where you are looking at the thing itself. */
.rb-elevate {
    margin-top: 22px;
    padding-top: 14px;
    border-top: 1px solid var(--rb-line);
    display: grid;
    gap: 8px;
    max-width: 560px;
}

.rb-empty, .rb-error, .rb-gate { padding: 22px; color: var(--rb-quiet); }

/* Ruby's greeting, and the things worth asking her. Sat at the top of the column rather
   than centred in it: the reader's eye goes to the box, and the invitations are what they
   read on the way. */
.rb-greeting { padding: 16px 14px 4px; }
.rb-hello, .rb-hello-more { color: var(--rb-quiet); margin: 0 0 12px; }
/* Home's first line, which says what the column is, so it is read rather than skimmed. */
.rb-hello-home { color: var(--rb-ink); }
.rb-invites { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.rb-invite {
    display: block;
    width: 100%;
    text-align: left;
    font: inherit;
    color: var(--rb-red);
    background: transparent;
    border: 1px solid var(--rb-line);
    border-radius: 3px;
    padding: 7px 10px;
    cursor: pointer;
}
.rb-invite:hover, .rb-invite:focus-visible {
    border-color: var(--rb-red);
    background: var(--rb-red-wash);
}
.rb-error { color: var(--rb-red); }
.rb-gate { font-size: 16px; }

/* -- the lightbox over the graph ---------------------------------------------- */

.rb-lightbox {
    position: fixed;
    inset: 0;
    background: rgba(23, 19, 15, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    z-index: 50;
}

.rb-lightbox[hidden] { display: none; }

.rb-lightbox-inner {
    background: var(--rb-panel);
    border-radius: 6px;
    max-width: 1000px;
    width: 100%;
    max-height: 100%;
    overflow: auto;
    padding: 22px 26px;
    position: relative;
}

/* Sticky rather than absolute, and floated right, because the panel it sits in is the
   scroll container: a quotes table thirteen thousand pixels tall put the close button
   four thousand pixels above the viewport with nothing else to click. */
.rb-close {
    position: sticky;
    top: 6px;
    float: right;
    z-index: 3;
    border: 0;
    background: var(--rb-panel);
    border-radius: 50%;
    width: 32px;
    height: 32px;
    font-size: 24px;
    line-height: 1;
    color: var(--rb-quiet);
    cursor: pointer;
}
.rb-close:hover { color: var(--rb-ink); }

/* The privacy panel. A native modal dialog rather than another lightbox, because it has to
   hold focus and sit above everything, including an open lightbox, and the top layer does
   both without a z-index to keep in step. Same paper and the same veil as the lightbox. */
.rb-privacy {
    border: 0;
    border-radius: 6px;
    background: var(--rb-panel);
    color: var(--rb-ink);
    width: min(520px, calc(100vw - 32px));
    max-height: calc(100vh - 32px);
    overflow: auto;
    padding: 22px 26px;
    font-family: var(--rb-serif);
    font-size: 15px;
    line-height: 1.45;
}
.rb-privacy::backdrop { background: rgba(23, 19, 15, 0.45); }
.rb-privacy h2 { margin: 0 0 10px; }
.rb-privacy p { margin: 0 0 10px; }
.rb-privacy-choice {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 0 0 10px;
    cursor: pointer;
}
.rb-privacy-choice input { margin-top: 5px; flex: none; accent-color: var(--rb-red); }
.rb-privacy .rb-note { font-size: 13px; color: var(--rb-quiet); }
.rb-privacy .rb-note a { color: var(--rb-red); }
/* Save on the right whether or not Cancel is there to push it. */
.rb-privacy .rb-ask-row { justify-content: flex-end; margin-top: 14px; }

/* A hundred rows down, the header is what tells stance from bearing. */
.rb-lightbox thead th {
    position: sticky;
    top: 0;
    background: var(--rb-panel);
    z-index: 2;
}

.rb-lightbox h2 { font-size: 22px; margin: 0 0 4px; }
.rb-lightbox h3 { margin: 18px 0 6px; }
.rb-kind { color: var(--rb-quiet); font-weight: 400; font-size: 13px; }
.rb-note { color: var(--rb-quiet); font-size: 12px; margin: 2px 0 8px; }

.rb-lightbox pre {
    background: var(--rb-wash);
    border: 1px solid var(--rb-line);
    border-radius: 4px;
    padding: 10px 12px;
    font-size: 12px;
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 340px;
    overflow: auto;
}

/* Wide content scrolls inside itself; the page never scrolls sideways. */
.rb-table { width: 100%; border-collapse: collapse; font-size: 13px; display: block; overflow-x: auto; }
.rb-table th, .rb-table td { border-bottom: 1px solid var(--rb-line); padding: 6px 8px; text-align: left; vertical-align: top; }
.rb-table th { color: var(--rb-quiet); font-weight: 600; font-size: 12px; }
/* Width is decided by what a column holds, not shared out equally. `width: 1%` with
   nowrap is the auto-layout idiom for "shrink to your content": a flag reading True, a
   category and the button column stop claiming a share, and what they hand back goes to
   the prose. The quote keeps a floor so it can never be squeezed into a ribbon again,
   and a ceiling so a single long passage cannot push everything else off the edge.
   Which class a column gets is worked out in page.js from the rows themselves. */
.rb-quote { min-width: 20em; max-width: 460px; }
.rb-col-tight { width: 1%; white-space: nowrap; }
.rb-col-wide { min-width: 14em; }
.rb-col-act { width: 1%; white-space: nowrap; }

.rb-link { border: 0; background: none; padding: 0; color: var(--rb-red); text-decoration: underline; cursor: pointer; font-size: 12px; }
.rb-table .rb-link { text-align: left; }

.rb-source { white-space: pre-wrap; font-size: 13px; line-height: 1.6; }
.rb-source mark { background: var(--rb-mark); padding: 1px 0; }

/* -- an authoring run --------------------------------------------------------- */

.rb-chat {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    min-height: 0;
    background: var(--rb-panel);
}
/* Each child holds its own row, so hiding one leaves the other two where they are. Under
   auto-placement a `display: none` first child slides the rest up a row, and the row that
   should scroll the conversation goes to the compose box instead. */
#rb-chat-head { grid-row: 1; }
#rb-said { grid-row: 2; }
#rb-ask { grid-row: 3; }

/* Which chat you are in, and the way to start another. */
.rb-chat-head {
    display: flex;
    gap: 8px;
    align-items: center;
    padding: 8px 12px;
    border-bottom: 1px solid var(--rb-line);
}
.rb-chat-head .form-select { flex: 1; min-width: 0; }
.rb-chat-head[hidden] { display: none; }

/* Ruby's mark, at the head of the row rather than on one control in it: the row is hers,
   the chat and the model both. `flex: none` because the two dropdowns beside it are
   `flex: 1` and must go on splitting what is left evenly; an earlier version wrapped the
   mark and the model picker in a container, and a nested flex container does not take an
   even share, which cost that box 47px. The stone is drawn from the page's own reds so it
   cannot drift from the ink. */
.rb-ruby-mark { display: block; width: 18px; height: 18px; flex: none; }
/* Her name and what she is, beside the stone. Home only, where the controls that fill this
   row in a project are all hidden; the rule that shows it is with the other home rules. */
.rb-ruby-name { display: none; color: var(--rb-red); font-size: 16px; line-height: 1.2; }
.rb-ruby-role {
    margin-left: 4px;
    font-family: var(--rb-sans);
    font-size: 12px;
    color: var(--rb-quiet);
}
.rb-ruby-stone { fill: var(--rb-red); }
.rb-ruby-shade { fill: var(--rb-red-deep); }
.rb-ruby-facets {
    fill: none;
    stroke: var(--rb-red-wash);
    stroke-width: 1;
    stroke-linejoin: round;
    opacity: .8;
}

/* The pencil on the two lines at the head of a question panel. Both lines are editable
   in place and neither said so, so the mark the chat already uses says it here too: the
   rule that reveals it lives with `.rb-edit` above and names both hosts, rather than a
   second copy of the same fade. Room is kept for it so a heading never runs underneath. */
.rb-q-head { position: relative; }
.rb-q-head > h2, .rb-q-head > .rb-q-brief { padding-right: 22px; }
.rb-q-head > .rb-edit { top: 2px; right: 0; font-size: 14px; }

/* -- a question's name, and the brief under it ----------------------------------- */
/* The name is the heading; the brief is what somebody typed and stays whole beneath it.
   A stand-in taken from the opening of the brief is drawn lighter than a name somebody
   chose, because presenting the two the same way would let a derived name pass for a
   decision. */
.rb-q-brief { margin: -4px 0 10px; color: var(--rb-quiet); white-space: pre-wrap; }
#rb-q-title[data-named="false"] { color: var(--rb-quiet); font-style: italic; }
.rb-run-name.rb-unnamed { color: var(--rb-quiet); }
.rb-report-brief { color: var(--rb-quiet); margin: -6px 0 14px; }

.rb-said-list { overflow-y: auto; padding: 14px; min-height: 0; }

.rb-ask {
    border-top: 1px solid var(--rb-line);
    padding: 10px 12px;
    display: grid;
    gap: 8px;
}

.rb-ask-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.rb-ask textarea { resize: vertical; }

.rb-said { border: 1px solid var(--rb-line); border-radius: 6px; padding: 10px 12px; margin-bottom: 10px; background: var(--rb-panel); }
/* Who is speaking, as a red label in the margin rather than as a bold word in the text.
   It is furniture: it names the voice and is never read as part of what was said. */
.rb-said strong {
    display: block;
    font-family: var(--rb-sans);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-red);
    margin-bottom: 4px;
}
/* The other voice is the quieter one, so its label steps back to grey. */
.rb-them strong { color: var(--rb-quiet); }
.rb-said p { margin: 0 0 6px; }
.rb-said p:last-child { margin-bottom: 0; }
.rb-breakdown { margin: 0 0 6px; padding-left: 18px; }
/* Her answer leads and is set as ordinary prose. Her reading of the question sits under
   it, quieter, because it is what to argue with rather than what was asked for. */
.rb-reading { color: var(--rb-quiet); }
.rb-them { background: var(--rb-wash); }
/* Named for what it is, not for a status. `rb-waiting` is already a run status chip,
   and the two collided: the amber chip lost its white text to this rule. */
.rb-thinking {
    color: var(--rb-quiet);
    display: grid;
    grid-template-columns: auto auto;
    gap: 2px 10px;
    align-items: center;
    background: linear-gradient(100deg, var(--rb-panel) 30%, var(--rb-wash) 50%, var(--rb-panel) 70%) 0 0 / 300% 100%;
    animation: rb-wait 2.4s ease-in-out infinite;
}
.rb-thinking [data-elapsed] { font-variant-numeric: tabular-nums; font-size: 12px; }
.rb-thinking [data-thinking-note] { grid-column: 1 / -1; margin-top: 4px; }

/* Three dots that carry the beat, so a minute of silence does not read as a page that
   has stopped. It says something is happening and claims nothing about how far along. */
.rb-dots { display: inline-flex; gap: 4px; }
.rb-dots i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--rb-quiet);
    animation: rb-dot 1.2s ease-in-out infinite;
}
.rb-dots i:nth-child(2) { animation-delay: 0.15s; }
.rb-dots i:nth-child(3) { animation-delay: 0.3s; }

@keyframes rb-dot {
    0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
    30% { opacity: 1; transform: translateY(-3px); }
}
@keyframes rb-wait {
    0% { background-position: 100% 0; }
    100% { background-position: 0 0; }
}

/* The one bar that says work is happening, wherever it is happening.

   Across the very top, over the dark bar, because that is the one part of the page that is
   on the screen whichever column you are in and whatever you have scrolled to. Fixed rather
   than a grid row, so it can appear and go without moving anything under it. Two pixels: a
   run takes minutes and this has to be noticeable for all of them without being something
   to look at. It reports nothing about progress, which is honest, since a run reports
   nothing until a step lands. */
/* One bar, drawn in two places: across the top of the window, and across the top edge of
   whichever column is waiting. Steve, 4 September 2026, of the window one: "good but
   hardly visible". It was 2px of pale yellow fading into cream over whatever happened to
   be behind it, so on a light page there were stretches of it that were not there at all.
   It now has a track, so the whole width reads as a bar whether or not the highlight is
   passing, and the highlight is the red the rest of the page uses for anything live. */
.rb-busy,
.rb-atwork::before {
    height: 3px;
    background-color: var(--rb-red-wash);
    background-image: linear-gradient(90deg,
        transparent, var(--rb-red), var(--rb-red-deep), var(--rb-red), transparent);
    background-size: 40% 100%;
    background-repeat: no-repeat;
    animation: rb-busy-slide 1.15s linear infinite;
}

.rb-busy {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 50;
}
.rb-busy[hidden] { display: none; }

/* A column says on itself that it is working, so somebody reading the middle of the screen
   does not have to know to look at the top of the window. Only ever put on a column that
   does not scroll: `top: 0` inside a scrolling box is the top of the content, so the bar
   would slide away the moment anybody scrolled. `page.js` names the two that qualify.

   Not `rb-waiting`, which is already the amber chip for a run whose status is waiting.
   Naming this one that put an animated red bar across every one of those chips, which is
   the second time the same collision has been made in this file. */
.rb-atwork { position: relative; }
.rb-atwork::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 5;
    pointer-events: none;
}

@keyframes rb-busy-slide {
    from { background-position: -45% 0; }
    to   { background-position: 145% 0; }
}

/* Movement is a nicety and some people are made ill by it. The count and the words carry
   the whole message without it. */
@media (prefers-reduced-motion: reduce) {
    .rb-thinking, .rb-dots i { animation: none; }
    .rb-dots i { opacity: 0.6; }
    /* Still shown, because its whole message is that something is happening, and a still
       bar says that as well as a moving one. */
    .rb-busy, .rb-atwork::before {
        animation: none; background-position: 0 0; background-size: 100% 100%;
    }
}

/* A proposal has not run, so there are no statuses to draw and it is shown as it is. */
.rb-paper { padding: 18px 22px; max-width: 900px; }

/* -- the hanging margin ------------------------------------------------------- */

/* The thing the rubricator actually did: the label for a section is not above it but
   beside it, out in the margin, in red, while the prose keeps one clean edge all the way
   down the page. It is what makes a page of this scannable without any rules or boxes.

   Done with a float rather than a grid, because the panels are a flat run of h3 and p
   with no wrapper around a section, and grid auto-placement cannot put a label on the
   same row as the content that follows it. The float sits entirely inside the padding the
   container reserves for it, so it never shortens a line box, and the table and the
   pre blocks (which make their own formatting contexts) do not get pushed off it either.

   `clear: left` is the safety valve for a section shorter than its own label: better a
   label pushed down than two labels on top of each other.

   Only where there is room. The margin costs 150px of width, which a phone does not have
   and a canvas column dragged narrow may not either; below that the labels go back to
   sitting above their sections, which is what they did before any of this. */
@media (min-width: 1100px) {
    .rb-question-panel, .rb-report, .rb-draft {
        padding-left: 172px;
        display: flow-root;
    }

    .rb-question-panel > h3,
    .rb-report > h3,
    .rb-draft > h3 {
        float: left;
        clear: left;
        width: 122px;
        margin: 0 0 0 -150px;
        padding-top: 5px;
        text-align: right;
        line-height: 1.4;
    }

    /* The label is out of the flow, so the space above a section has to be carried by the
       first thing in it. Without this every section runs into the one above. */
    .rb-question-panel > h3 + *,
    .rb-report > h3 + *,
    .rb-draft > h3 + * { margin-top: 26px; }

    /* Notes are the one section whose heading is wrapped, because the heading carries the
       edit and cancel buttons beside it. So the head goes out to the margin as a unit and
       takes its buttons with it, rather than the h3 alone floating and leaving them
       stranded in the text column. Any future section built the same way joins this rule.

       Right-aligned and stacked: the label, then what you can do to it under it. */
    .rb-question-panel > .rb-notes { display: flow-root; margin-top: 26px; }
    .rb-question-panel > .rb-notes > .rb-notes-head {
        float: left;
        clear: left;
        width: 122px;
        margin: 0 0 0 -150px;
        padding-top: 5px;
        flex-direction: column;
        align-items: flex-end;
        gap: 3px;
        text-align: right;
    }
    .rb-question-panel > .rb-notes > .rb-notes-head h3 { margin: 0; }
}
/* The first thing on a question nobody has run anything against, because that is the
   moment a person has no idea what the app wants from them. */
.rb-next {
    border-left: 3px solid var(--rb-mark);
    background: #fbf6e2;
    padding: 2px 14px 8px;
    margin: 12px 0;
}
.rb-next h3 { margin-top: 10px; }
.rb-next code { background: var(--rb-wash); padding: 1px 4px; border-radius: 3px; }
.rb-paper h2 { font-size: 21px; margin: 0 0 4px; }
.rb-run-row { display: flex; align-items: center; gap: 10px; margin: 12px 0; }
/* The standard, set apart from the workflow it belongs to, because agreeing to it is a
   separate decision and the person making it should be able to see where it starts. */
.rb-rubric {
    border-left: 3px solid var(--rb-line);
    padding-left: 14px;
    margin: 8px 0 4px;
}
.rb-rubric ul { margin: 0 0 10px; padding-left: 20px; }
.rb-rubric li { margin-bottom: 7px; }
/* The verdict name and its threshold on one line, what earns it under them, so a reader
   scanning for the levels is not reading sentences to find them. */
.rb-rubric li > div { margin-top: 1px; }
.rb-crit { margin: 14px 0 6px; }

/* Notes read as prose with a way in, rather than as a box waiting to be filled. */
.rb-notes-head { display: flex; align-items: baseline; gap: 10px; }
.rb-notes-head h3 { margin-bottom: 4px; }
.rb-notes .rb-prose p { margin: 0 0 6px; }

/* The draft, read as a document. */
.rb-draft { padding: 18px 22px; max-width: 900px; }
.rb-draft h2 { font-size: 21px; margin: 0 0 4px; }
.rb-draft-q { font-size: 15px; color: var(--rb-quiet); margin: 0 0 10px; }
.rb-steps { padding-left: 22px; margin: 14px 0; }
.rb-step { margin-bottom: 18px; }
.rb-step-head { margin: 0 0 4px; }
.rb-step p { margin: 0 0 6px; }
.rb-step ul { margin: 0 0 8px; padding-left: 20px; }
.rb-yaml > summary { cursor: pointer; color: var(--rb-quiet); font-size: 12px; }
.rb-refused {
    border-left: 3px solid var(--rb-red);
    padding-left: 14px;
    margin: 12px 0;
}
.rb-refused ul { margin: 6px 0; padding-left: 20px; }

/* Why a run stopped, printed where the chip saying `failed` is. The chip was the whole
   of it until 6 September 2026: the sentence explaining it sat in the ledger, which the
   page already read, and on no screen. */
.rb-stopped {
    border-left: 3px solid var(--rb-red);
    padding-left: 14px;
    margin: 12px 0;
}
/* Clamped, because a refusal can name every rubric on the project and an unclamped one
   pushes the offer of a fix off the bottom of the screen. Scrolls rather than truncates:
   the whole message is what Ruby is given, so the reader should be able to see it too. */
.rb-stopped ul {
    margin: 6px 0;
    padding-left: 20px;
    list-style: none;
    max-height: 14em;
    overflow-y: auto;
}
.rb-stopped li { margin-bottom: 8px; }
.rb-stopped li div { color: var(--rb-quiet); }
.rb-stopped .rb-run-row { flex-wrap: wrap; }

/* What a revision replaces, and what it costs against what it replaces. It sits directly
   above the Run button, which is the only place the difference can be noticed in time to
   act on it. The wash rather than a border where it is merely context; the red border
   where the revision opens half again as much reading, because that is a decision
   somebody is about to take without meaning to. */
.rb-replaces {
    background: var(--rb-wash);
    border-radius: 4px;
    padding: 10px 14px;
    margin: 12px 0;
    font-size: 13px;
}
.rb-replaces p { margin: 0 0 6px; }
.rb-replaces p:last-child { margin-bottom: 0; }
.rb-replaces-dearer {
    background: var(--rb-red-wash);
    border-left: 3px solid var(--rb-red);
}
.rb-yaml pre {
    background: var(--rb-wash);
    border: 1px solid var(--rb-line);
    border-radius: 4px;
    padding: 10px 12px;
    overflow-x: auto;
    margin-top: 8px;
}
.rb-yaml-key { color: var(--rb-red); font-weight: 600; }
.rb-yaml-comment { color: var(--rb-quiet); font-style: italic; }
.rb-rubric label { display: block; margin: 10px 0 4px; }
.rb-paper pre {
    background: var(--rb-wash);
    border: 1px solid var(--rb-line);
    border-radius: 4px;
    padding: 10px 12px;
    font-size: 12px;
    white-space: pre-wrap;
}
/* The four kinds of thing she says. The badge names the act; the colour only makes the
   badge findable, because three colours with no words were three colours. */
.rb-part {
    margin: 0 0 8px;
    padding-left: 10px;
    border-left: 3px solid var(--rb-hue, currentColor);
}
/* Inside one of these the badge and the rule carry the colour and the sentence stays ink.
   A paragraph set in ochre or in red is a paragraph read as a warning rather than read,
   and all four of these are ordinary things Ruby says. The classes still colour text
   where they are used on their own, which is what an error message wants. */
.rb-part.rb-cannot, .rb-part.rb-asks, .rb-part.rb-sure, .rb-part.rb-cost { color: var(--rb-ink); }
.rb-part .rb-badge { color: var(--rb-hue); border-color: var(--rb-hue); }
.rb-badge {
    display: inline-block;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    font-weight: 600;
    border: 1px solid currentColor;
    border-radius: 3px;
    padding: 0 5px;
    margin-right: 7px;
    vertical-align: 1px;
    white-space: nowrap;
}
.rb-cannot { color: var(--rb-red); --rb-hue: var(--rb-red); }
.rb-asks { color: #3f342c; --rb-hue: #3f342c; }
.rb-sure { color: var(--rb-waiting); --rb-hue: var(--rb-waiting); }
.rb-cost { color: var(--rb-quiet); --rb-hue: var(--rb-quiet); }

/* Folded, because a draft the check threw out is evidence rather than an answer. Shown
   at all, because a workflow that took three goes is a fact about the question. */
.rb-drafts {
    margin: 0 0 10px;
    color: var(--rb-quiet);
}
.rb-drafts > summary { cursor: pointer; font-size: 12px; }
.rb-drafts .rb-said { margin-top: 8px; background: var(--rb-wash); }
.rb-said details summary { cursor: pointer; color: var(--rb-quiet); font-size: 12px; }

/* -- a phone ------------------------------------------------------------------ */

/* Three columns will not fit and none of them is optional, so on a narrow screen the page
   shows one at a time and three pills say which. Steve, 3 September 2026: "mobile view can
   have 3 simple pills across the top to switch between the 3 columns."

   Pills rather than a menu because there are exactly three and they never change: a menu
   would hide two of them behind a tap and tell you nothing about where you are. */
.rb-pills { display: none; }

@media (max-width: 820px) {
    body { grid-template-rows: auto auto 1fr; }

    .rb-pills {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 6px;
        padding: 8px 10px;
        background: var(--rb-panel);
        border-bottom: 1px solid var(--rb-line);
    }
    .rb-pills button {
        border: 1px solid var(--rb-line);
        background: var(--rb-panel);
        color: var(--rb-quiet);
        border-radius: 999px;
        padding: 7px 4px;
        font-size: 13px;
        cursor: pointer;
    }
    .rb-pills button.rb-on {
        background: var(--rb-red);
        border-color: var(--rb-red);
        color: var(--rb-panel);
    }

    /* One column, whichever is showing, and the dividers have nothing to divide. The
       widen button has nothing to put away either, since the pills already do it. */
    .rb-main { display: block; }
    .rb-wide { display: none; }
    .rb-drag { display: none; }
    .rb-side, .rb-chat, .rb-canvas { display: none; height: 100%; }
    .rb-main[data-showing="questions"] .rb-side,
    .rb-main[data-showing="chat"] .rb-chat { display: grid; }
    .rb-main[data-showing="canvas"] .rb-canvas { display: flex; }

    /* The bar is tall enough on a phone that the project picker needs its own line. Last
       and a whole line wide: squeezed in beside the account it was ninety pixels wide and
       cut its own placeholder off at "Choose a p". */
    .rb-top { flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
    .rb-project { min-width: 0; flex: 1 1 100%; order: 3; }
    .rb-who { font-size: 11px; }
}

/* -- figures, one presentation per kind of answer ------------------------------ */

/* A figure is a finding, so it is set as one: its name as a heading, the answer at a size
   somebody can read across a desk, and what it was counted over underneath in small type
   as the caveat it is. They were rows of a five-column table whatever they held. */
.rb-figure { margin: 0 0 26px; }
.rb-figure h4 {
    font-size: 14px;
    margin: 0 0 8px;
    display: flex;
    align-items: baseline;
    gap: 10px;
}
.rb-figure-walk { font-weight: 400; }
.rb-figure-over { margin: 6px 0 0; }

/* One number is the whole answer, so it is allowed to be a number rather than a cell. */
.rb-fig-one {
    font-size: 26px;
    font-weight: 600;
    margin: 0;
    font-variant-numeric: tabular-nums;
}

.rb-fig { border-collapse: collapse; font-size: 14px; }
.rb-fig th, .rb-fig td { padding: 4px 12px; }
.rb-fig tbody th, .rb-fig thead th { font-weight: 600; text-align: left; }
/* Lined up on the digits, so a column of numbers can be compared by eye rather than read
   one at a time. */
.rb-num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* A breakdown is a distribution, and a column of numerals hides the shape of one. The bar
   sits behind its number rather than beside it, so the numbers stay in one column and
   still line up. */
.rb-fig-breakdown tbody td { position: relative; min-width: 130px; }
.rb-fig-breakdown .rb-bar {
    position: absolute;
    left: 0;
    top: 3px;
    bottom: 3px;
    background: var(--rb-bar);
    border-radius: 2px;
}
.rb-fig-breakdown .rb-num span { position: relative; }
.rb-fig-breakdown tfoot th, .rb-fig-breakdown tfoot td {
    border-top: 1px solid var(--rb-line);
    color: var(--rb-quiet);
}

/* A cross-tab is read by its margins as much as its cells, so the totals are on both
   edges and marked as not being part of the body. */
.rb-fig-xtab th, .rb-fig-xtab td { border: 1px solid var(--rb-line); }
.rb-fig-xtab thead th { color: var(--rb-quiet); }
.rb-fig-xtab .rb-edge { background: var(--rb-wash); color: var(--rb-quiet); }
.rb-fig-xtab tfoot th, .rb-fig-xtab tfoot td {
    background: var(--rb-wash);
    color: var(--rb-quiet);
}

/* Several single numbers in one asset, as a list rather than as several headings each
   with a number at 26px. Four of those down a page is a lot of room for four numbers and
   hides the only thing worth seeing, which is how they compare. */
.rb-fig-list { width: 100%; margin: 4px 0 10px; }
.rb-fig-list tbody th { font-weight: 600; padding-left: 0; }
.rb-fig-list td.rb-num { font-size: 17px; font-weight: 600; }
.rb-fig-list td:last-child { text-align: right; }

/* Said when a draft is carried from one question to another. Quiet, because it is a
   reassurance rather than a warning: the text is still there, and where it will go if
   sent has changed. */
.rb-kept { margin: 0 0 6px; padding: 0 2px; }

/* A run that ends in a verdict, marked as such. Not decoration: a verdict is a
   stipulation against a standard, and a description is a report of what is there, and the
   scepticism a reader should bring to the two is different. Worked out from whether the
   run produced a judgement, so nothing has to classify anything. */
.rb-verdict-mark {
    display: inline-block;
    margin-left: 8px;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--rb-mark);
    color: var(--rb-ink);
    font-size: 11px;
    font-weight: 600;
    vertical-align: middle;
}

/* -- rewriting a turn ----------------------------------------------------------- */

/* The pencil sits in the corner of the bubble rather than under the voice label, because
   the label is furniture and the button is not part of what was said. It is there at all
   times on a touch screen, where there is no hover to reveal it with. */
.rb-them { position: relative; }
.rb-edit {
    position: absolute;
    top: 6px;
    right: 8px;
    border: 0;
    background: none;
    padding: 2px 4px;
    line-height: 1;
    font-size: 13px;
    color: var(--rb-quiet);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.rb-them:hover .rb-edit,
.rb-them:focus-within .rb-edit,
.rb-q-head:hover .rb-edit,
.rb-q-head:focus-within .rb-edit { opacity: 1; }
.rb-edit:hover, .rb-edit:focus { color: var(--rb-red); opacity: 1; }
@media (hover: none) { .rb-edit { opacity: 1; } }

/* Space kept for the pencil, so a one-line question does not run under it. */
.rb-them .rb-words { padding-right: 18px; }

.rb-rewriting { border-color: var(--rb-red); }
.rb-rewriting strong { color: var(--rb-red); }
.rb-rewriting textarea { margin-bottom: 6px; }
.rb-rewrite-row { display: flex; gap: 8px; align-items: center; }

/* Where one chat stops being read and another starts. A rule with a word on it, because
   the turns on either side look identical and were said in different conversations. */
.rb-rewrote {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 2px 0 12px;
    font-family: var(--rb-sans);
    font-size: 10px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-quiet);
}
.rb-rewrote::before {
    content: '';
    flex: 0 0 18px;
    border-top: 1px solid var(--rb-line);
}
.rb-rewrote::after {
    content: '';
    flex: 1;
    border-top: 1px solid var(--rb-line);
}
.rb-rewrote .rb-link { font-size: 10px; letter-spacing: 0.09em; text-transform: uppercase; }

/* The workflows on a question.
   It takes its lines and spacing from .rb-table and adds only what this table needs. The
   markup carried `.rb-strategies` from the day it was written and nothing matched it, so
   the cost column has been ragged and the dates have been wrapping ever since. */
.rb-workflows { margin: 8px 0 4px; }
.rb-workflows th:nth-child(4),
.rb-workflows td:nth-child(4) {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.rb-workflows td:nth-child(3) { white-space: nowrap; }
.rb-workflows td:last-child { text-align: right; white-space: nowrap; }

/* -- a workflow as one document ------------------------------------------------- */

/* One scrolling document instead of a Result tab and a Workflow tab. Those were the same
   workflow read twice, the steps in one and what they produced in the other, and nothing
   on either said which step made which result. The blocks below are what the margin map
   will scroll to, so each is a real section with a name rather than a run of paragraphs. */
.rb-doc-head { margin-bottom: 18px; }
.rb-doc-head h2 { margin: 0 0 4px; }
.rb-doc-head .rb-run-meta { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }

/* A step and everything it made, kept together. The rule down the left is what says the
   results below a step belong to it rather than following it by accident. */
.rb-step-block {
    border-left: 2px solid var(--rb-line);
    padding-left: 14px;
    margin: 0 0 20px;
}
.rb-step-block > .rb-fold { margin: 0; }
.rb-step-block:focus-within,
.rb-step-block.rb-found { border-left-color: var(--rb-red); }

/* How the step went, under the step it went. Reads as a caption on the instruction above
   it rather than as a heading over the results below. */
.rb-ran { display: flex; align-items: center; gap: 8px; margin: 6px 0 10px; }

.rb-asset-block { margin: 12px 0 16px; }
.rb-asset-block h3 {
    font-size: 15px;
    margin: 0 0 2px;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

/* One prong of a fan-out, named by the item it ran for, so twelve codings of twelve
   countries can be told apart. */
.rb-prong { margin: 10px 0 0 10px; padding-left: 12px; border-left: 1px dotted var(--rb-line); }
.rb-prong-head {
    font-family: var(--rb-sans);
    font-size: 10px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--rb-quiet);
    margin: 0 0 4px;
}

/* Assets the workflow as it stands does not account for, which happens where it was
   rewritten between runs. Shown rather than dropped: silently losing a result off the
   page is the one failure this document must not have. */
.rb-orphans { border-top: 1px solid var(--rb-line); margin-top: 24px; padding-top: 12px; }
.rb-orphans > h3 { margin-top: 0; }

/* Where the map has sent the reader. Fades, so it says "here" without staying lit. */
@keyframes rb-found-fade {
    from { background: var(--rb-mark); }
    to { background: transparent; }
}
.rb-found { animation: rb-found-fade 2.4s ease-out; }

/* -- the margin map ------------------------------------------------------------- */

/* The document and its map, side by side inside the scroller. The map is sticky, so it
   stays on the screen the whole way down a workflow that runs to several screens.

   Wider than the 132px it was built at, because the nodes spread across the whole gutter
   now instead of sitting in a column down the middle. The widest rank over the 147 newest
   runs is 8 things across, which at 132px is exactly where a step box starts touching its
   neighbour. It only widens on a screen with the room for it: on a 1280 laptop the
   document beside it is already down to about 360px of prose. */
.rb-doc-with-map {
    display: grid;
    /* The document takes what it needs up to the width `.rb-paper` allows, and the pair
       is centred in whatever is left. Given the whole row, a `1fr` first column pinned
       the document to the left edge and the map to the right, with a hand's width of
       nothing between a drawing and the thing it draws. */
    grid-template-columns: minmax(0, 900px) 140px;
    justify-content: center;
    gap: 16px;
    align-items: start;
}
.rb-minimap {
    position: sticky;
    top: 12px;
    align-self: start;
    padding: 6px 0;
}
.rb-minimap canvas.rb-map { display: block; width: 100%; }

/* What the marks are. Steve, 6 September 2026: "the nodes are just the steps not the
   individual components" — of a map that had drawn the results as nodes all along and
   never said which they were. Shape carries it rather than colour, since colour on this
   map already means how a step went and whether a result exists.

   One row per mark rather than a line of them: the gutter is 140px and a key that wrapped
   mid-entry read as a list of unrelated words. The swatches are canvases drawn by the map's
   own code, so there is nothing here to keep in step with it. */
.rb-map-key {
    margin: 6px 0 0;
    font-family: var(--rb-sans);
    font-size: 10px;
    letter-spacing: 0.04em;
    color: var(--rb-quiet);
}

/* Shut, the key is one line at the foot of the map: the marks in a row and the word for
   what they are. A line per mark is the right reading and the wrong resting state, since
   the map is one viewport tall and every line of key is a line the map loses. */
.rb-map-key > summary {
    display: flex;
    align-items: center;
    gap: 5px;
    cursor: pointer;
    list-style: none;
    padding: 2px 0;
}
.rb-map-key > summary::-webkit-details-marker { display: none; }
.rb-map-key[open] > summary { color: var(--rb-red); }
.rb-map-marks { display: flex; align-items: center; gap: 2px; }
.rb-map-items {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    padding: 2px 0 0;
}
.rb-map-item { display: flex; align-items: center; gap: 4px; }

/* The names of everything lit, while the pointer is on the map. They hang to the LEFT of
   the map, over the document, because the gutter is 140px and a name does not fit in it;
   the document under them is being pointed at rather than read at that moment. Nothing
   here takes the pointer, or moving towards a name would move off the node that drew it
   and the whole layer would vanish under the cursor. */
.rb-map-tips {
    position: absolute;
    inset: 6px 0 0 0;
    pointer-events: none;
    z-index: 3;
}
.rb-map-tip {
    position: absolute;
    right: calc(100% - 6px);
    transform: translateY(-50%);
    max-width: 42ch;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    padding: 1px 6px;
    border-radius: 3px;
    background: var(--rb-paper, #fbfaf7);
    box-shadow: 0 1px 3px rgba(23, 19, 15, 0.18);
    font-family: var(--rb-sans);
    font-size: 10.5px;
    line-height: 1.5;
    color: var(--rb-quiet);
}
/* The one under the pointer, said in the ink the rest of the page uses for what you are on. */
.rb-map-tip.rb-on { color: var(--rb-ink, #17130f); font-weight: 600; }
.rb-map-item canvas, .rb-map-marks canvas { width: 16px; height: 16px; flex: none; }

/* Room for the wider gutter, measured in canvas rather than in window: 900px is the
   document at its full width with the map still beside it. */
@container canvas (min-width: 1120px) {
    .rb-doc-with-map { grid-template-columns: minmax(0, 900px) 176px; }
}

/* Under this the document is down to about 360px of prose beside the map, which is where
   a step box starts touching its neighbour, and a map nobody can read is worse than no
   map. The gutter goes with it rather than leaving a hole. */
@container canvas (max-width: 560px) {
    .rb-doc-with-map { grid-template-columns: minmax(0, 1fr); gap: 0; }
    .rb-minimap { display: none; }
}

/* -- folding a step, and folding a result --------------------------------------- */

/* Steve, 5 September 2026: "maybe we can make each step and result collapsible too?" A
   workflow with four coding steps is many screens of instruction, and somebody looking for
   one figure should be able to put the rest away. Open by default: a document that opens
   closed is a document nobody reads. */
.rb-fold > summary {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 3px 0;
    border-radius: 3px;
}
.rb-fold > summary::-webkit-details-marker { display: none; }
.rb-fold > summary:hover { background: var(--rb-wash); }
/* The triangle, drawn rather than borrowed, so it turns with the fold and sits on the
   baseline of the words beside it. */
.rb-fold > summary::before {
    content: '';
    flex: 0 0 auto;
    width: 0;
    height: 0;
    border-left: 4px solid var(--rb-quiet);
    border-top: 4px solid transparent;
    border-bottom: 4px solid transparent;
    transform: translateY(-1px);
    transition: transform 0.12s ease;
}
.rb-fold[open] > summary::before { transform: translateY(1px) rotate(90deg); }
.rb-fold-body { padding-left: 12px; }

/* The step's number, which used to come from an ordered list and now has to be drawn,
   since the line it sits on is the one you press to fold the step. */
.rb-n {
    flex: 0 0 auto;
    font-variant-numeric: tabular-nums;
    color: var(--rb-quiet);
    font-size: 12px;
    min-width: 12px;
}
/* A project on the home list with work going on in it. The widest of the three levels
   Steve set out on 8 September 2026: the page bar for anything running, a project's or
   question's own row for that one, a column's bar for that column. The row is tinted
   rather than animated, because a list of twenty rows each pulsing is a list nobody can
   read; the dot beside the name is the moving part. */
.rb-row-atwork > td { background: var(--rb-wash); }
.rb-workdot {
    display: inline-block;
    width: 7px;
    height: 7px;
    margin-left: 7px;
    vertical-align: middle;
    border-radius: 50%;
    background: var(--rb-running);
    animation: rb-dot 1.4s infinite;
}

/* How far a step has got, on the line you press to fold it. Folded, a running workflow
   showed six names and nothing else, so the only way to see which had finished was to open
   all six (Steve, 8 September 2026). Pushed to the right end of the row, because the marks
   line up in a column there and the eye reads down them. */
.rb-stepstate {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 13px;
    line-height: 1;
    /* The bare `.rb-succeeded` rules paint a chip's background, which is right for a status
       word and wrong for a glyph sitting on the fold line. */
    background: none;
    color: var(--rb-quiet);
}
.rb-stepstate.rb-succeeded,
.rb-stepstate.rb-skipped   { color: var(--rb-succeeded); }
.rb-stepstate.rb-failed    { color: var(--rb-red); }
.rb-stepstate.rb-running,
.rb-stepstate.rb-waiting   { color: var(--rb-running); }

/* The mark itself, drawn from borders rather than written as a character. `✓` was in the
   DOM and invisible on STEVE-DELL, which has form for missing glyphs, and a mark nobody can
   see reports a state that never reaches the reader. Borders are drawn by the layout engine
   and depend on no font. */
.rb-stepstate > i {
    display: inline-block;
    vertical-align: middle;
    font-style: normal;
}
/* A tick: two borders of a box, rotated. */
.rb-stepstate.rb-succeeded > i,
.rb-stepstate.rb-skipped > i {
    width: 5px;
    height: 9px;
    border: solid currentColor;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg) translateY(-2px);
}
/* An inherited step is a tick nobody paid for, so it is drawn hollow. */
.rb-stepstate.rb-skipped > i { opacity: 0.55; }

/* What a change above a step does to it: on a draft, what running it will do; on a run,
   that a later run did the step again. Red is kept for the steps that will be done again,
   since those are what a change costs and what a reader has to look at. */
.rb-will {
    font-size: 11px;
    line-height: 1.5;
    padding: 0 6px;
    margin-left: 6px;
    border: 1px solid var(--rb-faint);
    border-radius: 8px;
    color: var(--rb-quiet);
    white-space: nowrap;
}
.rb-will.rb-will-run   { border-color: var(--rb-red); color: var(--rb-red); }
.rb-will.rb-will-reuse { border-color: var(--rb-succeeded); color: var(--rb-succeeded); }
/* A step that would be taken although the code behind it has been revised since: the one
   place the page offers to do a step again. Amber, between taken and done again. */
.rb-will.rb-will-stale { border-color: var(--rb-waiting); color: var(--rb-waiting); }
.rb-stale-why { color: var(--rb-waiting); }
.rb-forecast,
.rb-redone-head {
    border-left: 3px solid var(--rb-red);
    background: var(--rb-red-wash);
    padding: 6px 10px;
    margin: 8px 0 12px;
}
.rb-forecast p,
.rb-redone-head p { margin: 2px 0; }
.rb-will-note,
.rb-redone-note { margin-top: 0; }
/* A cross, from the same box with both diagonals. */
.rb-stepstate.rb-failed > i {
    width: 10px;
    height: 10px;
    position: relative;
}
.rb-stepstate.rb-failed > i::before,
.rb-stepstate.rb-failed > i::after {
    content: '';
    position: absolute;
    left: 0;
    top: 4px;
    width: 10px;
    height: 2px;
    background: currentColor;
}
.rb-stepstate.rb-failed > i::before { transform: rotate(45deg); }
.rb-stepstate.rb-failed > i::after  { transform: rotate(-45deg); }
/* Work in hand: a filled dot that breathes, so a glance tells a live step from a done one. */
.rb-stepstate.rb-running > i,
.rb-stepstate.rb-waiting > i,
.rb-stepstate.rb-stale > i {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: currentColor;
    animation: rb-dot 1.4s infinite;
}
.rb-stepstate.rb-stale > i { background: none; border: 2px solid currentColor; animation: none; }

/* Only drawn where the denominator is real, which is a fan-out with a known number of
   prongs. See `model.stepProgress`, which returns no total for the rest. */
.rb-stepbar {
    flex: 0 0 auto;
    margin-left: auto;
    width: 46px;
    height: 3px;
    border-radius: 2px;
    background: var(--rb-wash);
    overflow: hidden;
}
.rb-stepbar > span {
    display: block;
    height: 100%;
    background: var(--rb-succeeded);
    transition: width 0.4s ease;
}
/* Where a bar is drawn it has taken the right-hand slot, so the mark stops claiming it. */
.rb-stepbar + .rb-stepstate { margin-left: 8px; }

.rb-step-block > .rb-fold > summary > strong { font-size: 15px; }
.rb-asset-block > .rb-fold > summary {
    font-family: var(--rb-sans);
    font-size: 13px;
    font-weight: 600;
}

/* -- ordering the question list -------------------------------------------------- */

/* On hover, because an order is changed rarely and read constantly. The one in force
   stays visible at all times; the others appear when the pointer is over the column, so
   the list is not carrying a permanent row of controls nobody is using. */
.rb-sorts {
    display: flex;
    gap: 2px;
    align-items: center;
    padding: 2px 12px 6px;
    min-height: 22px;
}
.rb-sort {
    border: 0;
    background: none;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: var(--rb-sans);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--rb-quiet);
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.12s ease;
}
.rb-sort.rb-on { opacity: 1; color: var(--rb-red); background: var(--rb-red-wash); }
/* Which way the order in force is running. `aria-pressed` says which order was chosen and
   nothing about its direction, so the arrowhead is the only thing on the screen that
   answers "and which end is the top?". */
.rb-sort-way { margin-left: 3px; font-size: 9px; line-height: 1; }
.rb-side:hover .rb-sort,
.rb-sorts:focus-within .rb-sort { opacity: 1; }
.rb-sort:hover { background: var(--rb-wash); color: var(--rb-ink); }
.rb-sort.rb-on:hover { background: var(--rb-red-wash); color: var(--rb-red); }
/* No hover on a touch screen, so the choice has to be there to be made. */
@media (hover: none) { .rb-sort { opacity: 1; } }

/* Which run of a workflow you are reading. Inline in the meta line, because it is one
   more fact about this run rather than a control over the page. */
.rb-run-pick {
    display: inline-block;
    width: auto;
    max-width: 100%;
    font-size: 12px;
    vertical-align: baseline;
}

/* What a run cost the world, beside what it cost in credits. Quieter than the credits,
   because it is an estimate and says so when you hover it. */
.rb-flame { color: var(--rb-quiet); white-space: nowrap; cursor: help; }

/* -- the breadcrumb -------------------------------------------------------------- */

/* Where you are, and the one place above it. Steve, 5 September 2026: "tidy this so they
   look of even weight and make it clear that Workflow is selected."
   They were a 13px underlined red link beside an 11px uppercase red label, which read as
   two unrelated things shouting at each other and said nothing about which one you were
   on. Same size and same family now, so the line reads as one trail; what says which is
   which is colour, weight and the mark between them. */
.rb-caption { gap: 10px; }
.rb-link.rb-back {
    font-size: 12px;
    font-weight: 400;
    color: var(--rb-quiet);
    text-decoration: none;
    border-bottom: 1px solid transparent;
    padding-bottom: 1px;
    margin-right: 0;
}
.rb-link.rb-back:hover,
.rb-link.rb-back:focus { color: var(--rb-red); border-bottom-color: var(--rb-red); }

.rb-where {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.02em;
    text-transform: none;
    color: var(--rb-ink);
}
/* The turn in the trail, drawn only where there is something to turn from. */
.rb-link.rb-back + .rb-where::before {
    content: '\203A';
    color: var(--rb-line);
    font-weight: 400;
    margin-right: 10px;
}

/* A question with something running on it, in the list where somebody waits it out.
   The same slow sweep the chat uses while Ruby is thinking, so "working" looks like one
   thing across the page rather than two. A left rule as well as the sweep, because a
   moving background alone is easy to miss on a list somebody is scanning. */
.rb-q-running {
    position: relative;
    background: linear-gradient(100deg, transparent 30%, var(--rb-red-wash) 50%,
                                transparent 70%) 0 0 / 300% 100%;
    animation: rb-wait 2.4s ease-in-out infinite;
}
/* The same bar the top of the window draws, on the row itself. Steve, 6 September 2026,
   of the first attempt: "the running question does have an extra grey bar but it is not
   our recognisable red animation". It swept in `--rb-wash` under a slate rule, which is
   the colour of the *status* running rather than the colour of work happening, so the row
   said something was going on in a register nothing else on the page uses. Work is red
   and it slides, here and in the window bar and on the run row, and it is one thing
   because a signal that looks different in three places is three signals. */
.rb-q-running::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 3px;
    background-color: var(--rb-red-wash);
    background-image: linear-gradient(90deg,
        transparent, var(--rb-red), var(--rb-red-deep), var(--rb-red), transparent);
    background-size: 40% 100%;
    background-repeat: no-repeat;
    animation: rb-busy-slide 1.15s linear infinite;
    pointer-events: none;
}
/* Somebody who has asked not to see motion is still told, in the one way that does not
   move. */
@media (prefers-reduced-motion: reduce) {
    .rb-q-running { animation: none; background: var(--rb-red-wash); }
    .rb-q-running::after { animation: none; background-image: none;
                           background-color: var(--rb-red); }
}

/* -- writing it up --------------------------------------------------------------- */

.rb-report-wrap { max-width: 46em; }
.rb-report-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    border-bottom: 1px solid var(--rb-line);
    padding-bottom: 10px;
    margin-bottom: 14px;
}
.rb-report-out { display: flex; gap: 6px; flex-wrap: wrap; }

/* What is in it and in what order, which is an editorial decision rather than a fact
   about the ledger, so it is a control rather than a reading. */
.rb-report-order {
    margin: 0 0 20px;
    padding-left: 20px;
    font-family: var(--rb-sans);
    font-size: 12px;
    color: var(--rb-quiet);
}
.rb-report-order li { margin: 3px 0; }
.rb-report-order .rb-link { font-size: 13px; padding: 0 3px; text-decoration: none; }
.rb-report-order .rb-link[disabled] { opacity: 0.25; cursor: default; }
.rb-report-order span { color: var(--rb-ink); }

/* Named rather than counted: choosing nine questions and getting six, with nothing saying
   which three went missing, is the failure this app exists against. */
.rb-report-left-out { margin-bottom: 16px; font-size: 13px; }
.rb-report-left-out ul { margin: 4px 0 0; padding-left: 20px; }

.rb-report-front { margin-bottom: 24px; }
.rb-report-front h1 { margin-bottom: 4px; }
.rb-report-contents { margin-bottom: 28px; }
.rb-report-contents ol { padding-left: 20px; }

/* The controls are for the person arranging it, not for whoever it is handed to. */
@media print {
    .rb-report-bar, .rb-report-order, .rb-report-left-out,
    .rb-report-wrap > [data-said] { display: none !important; }
    .rb-report-wrap { max-width: none; }
}

/* The record behind a report, printed after the annex in the copy a commissioner is sent.
   It is never on the screen, so these rules are for paper. A step carries its own button
   for changing the model, which means nothing on a page; a fingerprint is one unbroken
   string that has to be allowed to wrap; and the record opens a page of its own, with each
   part after the first starting another. Outside the print rules so the browser's own
   drawing of the file, when the server cannot typeset it, obeys them too. */
.rb-report-record button { display: none !important; }
.rb-report-record code { overflow-wrap: anywhere; font-size: 0.92em; }
.rb-report-record .rb-record-step { margin-bottom: 14px; }
@media print {
    .rb-report-record-open, .rb-report-record { break-before: page; }
    .rb-report-record-open + .rb-report-record { break-before: auto; }
    .rb-report-record tr { break-inside: avoid; }
}

/* The leaf html2pdf draws from. It carries the same classes as the report on the screen
   and so gets the same look for nothing; only what a canvas cannot do is said again here.

   Two things about how it is staged, both learned by getting a blank file. It must be IN
   the document, because html2pdf sizes the page it draws onto from the element's own
   measured width and a detached element measures zero. And the parking off the side of the
   window belongs to the wrapper around it, never to this element: html2pdf clones what it
   is given into its own capture box, and a clone that carries `position: fixed; left:
   -10000px` lands ten thousand pixels outside that box, which draws a blank page and
   reports success. The measure stays with .rb-report-wrap, so there is one place it is
   decided. */
.rb-report-pdf { width: 100%; }

/* Two things the screen can do that a page cannot. A wide table scrolls sideways here and
   there is nowhere to scroll to on paper, so it lays out in full instead. And the hanging
   red margin comes and goes with the WINDOW's width, which would make the same report
   come out two different shapes depending on the size of the window it was asked for in;
   the labels sit above their sections in the file, always. */
.rb-report-pdf .rb-table { display: table; overflow: visible; }
.rb-report-pdf .rb-report { padding-left: 0; }
.rb-report-pdf .rb-report > h3 { float: none; width: auto; margin: 22px 0 6px;
                                 text-align: left; }
.rb-report-pdf .rb-report > h3 + * { margin-top: 0; }

/* The page box, said once, for both of the ways a report becomes a PDF.

   The server route sends this whole stylesheet to WeasyPrint, which honours `@page`; the
   browser route is told the same margins in millimetres through html2pdf's own options,
   because it lays a picture onto a page rather than typesetting one and has no page box to
   read. Two mechanisms, one set of numbers, and they are written next to each other here
   so that changing one without the other is hard to do by accident. */
@page {
    size: A4;
    margin: 14mm 12mm 16mm;
}

/* -- reading somebody else's published project ------------------------------------
   A signed-out visitor may read the record of a project whose owner has published it,
   and may change none of it. Hiding is not the permission: the anon role holds no
   EXECUTE on any of the writing functions, and `rubicon.may_write` refuses it besides.
   This is so that a control which cannot work is not offered, which was the fault the
   whole signed-out brief named.

   Selected by the data attribute each control's handler reads rather than by class, so
   the list here and the list of things that write are the same list. A control missed
   here still cannot write: it reaches `call` in ledger.js and comes back with a sentence
   saying signing in is needed. */
body.rb-readonly #rb-chat,
body.rb-readonly #rb-drag-mid,
body.rb-readonly #rb-pills [data-pill="chat"],
body.rb-readonly #rb-new-question,
body.rb-readonly #rb-newq-form,
body.rb-readonly [data-arch],
body.rb-readonly [data-arch-question],
body.rb-readonly [data-arch-run],
body.rb-readonly [data-stop],
body.rb-readonly [data-elevate],
body.rb-readonly [data-accept],
body.rb-readonly [data-withdraw],
body.rb-readonly [data-edit-notes],
body.rb-readonly [data-edit-field] { display: none; }

/* Two columns and one divider, since the conversation is the column that has gone. */
body.rb-readonly .rb-main {
    grid-template-columns: var(--rb-left, 300px) 5px minmax(0, 1fr);
}

@media (max-width: 820px) {
    body.rb-readonly .rb-pills { grid-template-columns: repeat(2, 1fr); }
}

/* Every link in the bar, whichever of the two it is: Sign out when somebody is signed in,
   Sign in when nobody is. Only the read-only one was styled, so the sign-out link fell
   through to the browser's own blue-and-underlined default in a page that has no other
   blue in it. The banner is a sentence in the bar rather than a strip of its own: the bar
   is where an account name would be, and this is what stands in for one. */
.rb-who a {
    color: var(--rb-red);
    text-decoration: none;
    border-bottom: 1px solid transparent;
}
.rb-who a:hover, .rb-who a:focus-visible { border-bottom-color: currentColor; }

/* What has moved in the world since an attempt ran. Two weights, because an edited document
   very likely changes the answer and our own bug fix probably does not, and a flag implying
   doubt where there is little would be worse than a sentence saying so. Steve, 8 September
   2026: "if the code has changed, only a very pale badge." */
.rb-moved { margin: 10px 0; }
.rb-moved-loud {
    border-left: 3px solid var(--rb-red);
    background: var(--rb-red-wash);
    padding: 7px 10px;
    margin: 0 0 6px;
    font-size: 13px;
}
.rb-moved-quiet {
    border-left: 3px solid var(--rb-quiet);
    padding: 5px 10px;
    margin: 0;
    font-size: 12px;
    color: var(--rb-quiet);
}

/* A small square button whose label is its icon.
 *
 * The words moved to the title and the aria-label, so the button still says what it is on
 * hover and to a screen reader. Square rather than the pill Bootstrap gives a text button,
 * because a single glyph in a wide button reads as a mistake.
 */
.rb-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 31px;
    padding-left: 0;
    padding-right: 0;
    flex: 0 0 auto;
}

/* -- home: no project open ------------------------------------------------------
   Every control in the left column is about a project, so on home each one offers
   something that cannot happen. The Project hub button was worse than inert: it opened
   the standing conversation of the project just closed. One class, set by
   `projectIsOpen`, rather than a `hidden` on each of them in five places. */
body.rb-noproject .rb-home-row,
body.rb-noproject .rb-nav,
body.rb-noproject .rb-newq-form,
body.rb-noproject .rb-filter,
body.rb-noproject .rb-sorts,
body.rb-noproject #rb-chat-pick,
body.rb-noproject #rb-ruby-model,
body.rb-noproject #rb-chat-new { display: none; }
body.rb-noproject .rb-ruby-name { display: inline; }

/* -- a field somebody else owns, at whatever length they wrote it ----------------
   The project description comes from Causal Map and has no length limit. A 500-word one
   filled the canvas and pushed the corpus, the questions, the runs and the chats off the
   bottom, so the project hub showed one paragraph and nothing it is a hub for. Capped in
   `vh` rather than in lines, because what has to stay on screen is the rest of the panel
   and that is measured against the window. */
.rb-longtext {
    max-height: 34vh;
    overflow-y: auto;
    /* Says it scrolls before anybody scrolls it: without the rule the text simply stops
       mid-sentence at the fold and reads as a truncation. */
    border-left: 2px solid var(--rb-line);
    padding-left: 12px;
    /* A cap in viewport height cuts wherever it lands, so the last line is usually sliced
       through the middle. Flush against the paragraph below, a half line of text reads as
       two paragraphs printed on top of each other, which is what it was taken for. The
       gap separates them and the fade says the slice is a fold rather than an ending. */
    margin: 0 0 16px;
    padding-bottom: 4px;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 20px), transparent);
}
.rb-longtext > p:first-child { margin-top: 0; }
.rb-longtext > p:last-child { margin-bottom: 0; }

/* -- a link that went somewhere already on the screen ---------------------------
   Ruby offers to open her draft and it is already the thing in the canvas, so pressing
   the link changes nothing and reads as broken. The canvas says where it went instead of
   moving. Same idea as `.rb-found` one level up: it says "here" and does not stay lit.
   The ring is drawn with an inset shadow rather than a border, so nothing reflows and the
   document underneath does not shift by a pixel while it plays. */
@keyframes rb-flash-fade {
    0%   { box-shadow: inset 0 0 0 3px var(--rb-red); }
    60%  { box-shadow: inset 0 0 0 3px var(--rb-red); }
    100% { box-shadow: inset 0 0 0 3px transparent; }
}
.rb-flash { animation: rb-flash-fade 0.9s ease-out; }

@media (prefers-reduced-motion: reduce) {
    /* Still drawn, because the whole message is "it is here". A single step rather than a
       fade says that without anything moving. */
    .rb-flash { animation: rb-flash-fade 0.9s steps(2, end); }
}
