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

body {
    min-height: 100vh;
    font-family: var(--md-font);
    font-size: 14px;
    line-height: 1.5;
    background: var(--md-background);
    color: var(--md-on-background);
}

a {
    color: inherit;
    text-decoration: none;
}

.wrapper {
    max-width: 100vw;
    padding: 24px;
    display: grid;
    grid-template-columns: 260px 1fr;
    column-gap: 24px;
    align-items: start;
}

/* The navigation stands still while the content scrolls: sticky to the
   viewport, scrolling on its own only when it is taller than the screen
   (the Sign out button must stay reachable on short displays). */
.wrapper > aside {
    position: sticky;
    top: 24px;
    max-height: calc(100vh - 48px);
    overflow-y: auto;
}

.content {
    display: flex;
    flex-direction: column;
    row-gap: 20px;
    min-width: 0;
    background: var(--md-surface-container-lowest);
    border-radius: var(--md-shape-xl);
    padding: 28px 32px;
    box-shadow: var(--md-elevation-1);
}

.content-heading {
    display: flex;
    align-items: center;
    justify-content: space-between;
    column-gap: 12px;
}

/* The left-anchored variant: back link and title sit together, and the
   action cluster below pushes itself to the far edge — space-between
   with several loose children spreads them across the whole width, which
   reads as scattered buttons rather than a header. */
.content-heading--left {
    justify-content: flex-start;
}

/* One cluster for a page's header verbs: tight, right-aligned, in one
   visual group — launch doors first, quiet doors after, danger last. */
.content-heading__actions {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

/* One project's sections, drawn under its heading on every page of it.
   The same chips the guide's nav wears (see components/docs.css), because
   they are the same thing — a set of sibling pages with one of them
   current — and a reader who has learnt one strip should not have to learn
   a second. Wraps at a narrow window rather than scrolling sideways: six
   short words on two lines are readable, and a horizontal scroller hides
   whichever tab is off the edge. */
.project-nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 14px 0 22px;
}

.project-nav a {
    display: inline-block;
    padding: 6px 14px;
    border-radius: var(--md-shape-full);
    color: var(--md-on-surface-variant);
    background: var(--md-surface-container);
    border: 1px solid var(--md-outline-variant);
}

.project-nav a:hover {
    border-color: var(--md-primary);
    color: var(--md-primary);
}

.project-nav a.active {
    color: var(--md-on-secondary-container);
    background: var(--md-secondary-container);
    border-color: transparent;
    font-weight: 600;
}

/* The project counted, as a row of tiles.

   Not a chart: three magnitudes with no shape to them and nothing to
   compare across time, which is exactly the case a stat tile answers
   better than any plot. The whole tile is the link into the tab that lists
   what it counts.

   The value carries the font's proportional figures on purpose —
   tabular-nums gives every digit the width of a zero, which reads loose at
   display sizes and is for columns of numbers that must line up, not for a
   standalone figure. */
.project-lede {
    color: var(--md-on-surface-variant);
    max-width: 92ch;
    margin-bottom: 20px;
}

.project-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: 12px;
    margin-bottom: 24px;
}

.project-stats__tile {
    display: grid;
    row-gap: 2px;
    padding: 14px 18px;
    background: var(--md-surface-container-low);
    border: 1px solid var(--md-outline-variant);
    border-radius: var(--md-shape-md);
}

.project-stats__tile:hover {
    border-color: var(--md-primary);
}

.project-stats__label {
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--md-on-surface-variant);
}

.project-stats__value {
    font-size: 28px;
    line-height: 34px;
    font-weight: 600;
    color: var(--md-on-surface);
}

.project-stats__detail {
    font-size: 12px;
    line-height: 1.45;
    color: var(--md-on-surface-variant);
}

/* The status tones. Each one dresses a word that already says the same
   thing, so a reader who cannot see the colour loses nothing — which is
   why there is no tone that appears without its noun. */
.project-stats__part--live {
    color: var(--md-primary);
}

.project-stats__part--warn {
    color: var(--md-warning);
}

.project-stats__part--bad {
    color: var(--md-error);
}

/* Under a list that holds more rows than it shows: what this page is a
   page of, and the way out at either end. The span sits on the left and
   the steps on the right, because the number is read and the links are
   aimed at. */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 16px;
    font-size: 13px;
    color: var(--md-on-surface-variant);
    font-variant-numeric: tabular-nums;
}

.pager__steps {
    display: flex;
    gap: 10px;
}

/* Two-column field layout for wide settings forms: the shared field
   partial flows into the grid, and only the wide controls (a textarea)
   span both columns — the create page keeps its single column simply by
   not wrapping the partial. */
.form-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 24px;
}

/* A centered working sheet for form pages: wide enough for two field
   columns, centered in the content area, and it releases the .form
   inside it from the 420px single-column default. */
.form-sheet {
    width: 100%;
    max-width: 960px;
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    row-gap: 20px;
}

.form-sheet .form {
    max-width: 100%;
}

/* The slim variant for short forms — login-sized columns, still
   centered: a three-field form stretched to the wide sheet reads as
   lost, not as roomy. */
.form-sheet--narrow {
    max-width: 480px;
}

.form-grid > .input-wrapper:has(textarea) {
    grid-column: 1 / -1;
}

/* A field whose hint is a paragraph takes the whole row on purpose —
   an odd field in the flow would knock every later pair out of its
   semantic column (git beside git, LLM beside LLM). */
.form-grid > .input-wrapper--wide {
    grid-column: 1 / -1;
}

/* The optional fold at a form's tail: spans both columns, its own inner
   grid mirrors the outer one, and the summary reads as a section heading
   with the "optional" note beside it. A plain details element — the
   console scripts no forms. */
.form-grid > .form-advanced {
    grid-column: 1 / -1;
    margin-top: 8px;
    border-top: 1px solid var(--md-outline-variant);
    padding-top: 12px;
}

.form-advanced__summary {
    cursor: pointer;
    font-weight: 500;
    color: var(--md-on-surface);
    margin-bottom: 12px;
}

.form-advanced__note {
    font-weight: 400;
    font-size: 13px;
    color: var(--md-on-surface-variant);
    margin-left: 8px;
}

/* The roles section: each rank is a block — the name with its published
   meaning, then one flex row where the form IS the row (the bound rank's
   edit form, or the folded bind form of an unbound one). */
.role-pool {
    margin: 14px 0;
}

.role-pool__title {
    margin: 0 0 6px;
    color: var(--md-on-surface);
}

.role-pool__note {
    font-size: 13px;
    color: var(--md-on-surface-variant);
}

.role-pool__unbound {
    margin: 2px 0 4px;
    font-size: 13px;
    color: var(--md-on-surface-variant);
}

.role-seat {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 4px 0;
}

.role-seat__form {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
}

.role-seat__control {
    flex: 1;
    min-width: 0;
}

/* The two number boxes of a rank's row hold a round count and a token
   count and need no more width than that; the selects take the rest. */
.role-seat__number {
    flex: 0 0 7.5rem;
}

/* The free-text model box is hidden by roles.js until the select says
   "other"; the attribute must win over the flex item's own display. */
.role-seat__control[hidden] {
    display: none;
}

/* Save and Unbind sit side by side on every bound rank's row: the shared button
   classes disagree on padding (10x24 vs 8x20), so equalize them here —
   same height, same weight, a matched pair. */
.role-seat .button,
.role-seat .action-button {
    padding: 8px 18px;
    font-size: 14px;
}

.role-pool__add {
    margin: 6px 0 0;
}

/* The fold toggle is a real click target, not a footnote: chip-sized,
   bordered, with a hover that says "press me". */
.role-pool__add summary {
    display: inline-block;
    cursor: pointer;
    font-size: 14px;
    padding: 8px 16px;
    border: 1px solid var(--md-outline-variant);
    border-radius: 999px;
    color: var(--md-on-surface);
    background: var(--md-surface-container-low);
}

.role-pool__add summary:hover {
    background: var(--md-surface-container-high);
}

.role-pool__add[open] summary {
    margin-bottom: 6px;
}

.role-pool__add form {
    margin-top: 6px;
}

/* A document field: the textarea and its upload control share one row —
   the file input sits small at the textarea's right, its picker button
   styled like the house chips. */
.upload-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

.upload-row .input-wrapper__input {
    flex: 1;
    min-width: 0;
}

/* The control column: the chip on top, the status line under it. The
   native "No file chosen"/filename text is hidden (font-size 0 — the
   button keeps its own explicit size) because browsers clip it beside
   the button instead of wrapping; upload.js writes the chosen name into
   the status line below. */
.upload-col {
    flex: 0 0 150px;
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.upload-row__file {
    font-size: 0;
    width: 100%;
}

.upload-row__file::file-selector-button {
    cursor: pointer;
    font-size: 14px;
    padding: 8px 16px;
    margin: 0;
    border: 1px solid var(--md-outline-variant);
    border-radius: 999px;
    color: var(--md-on-surface);
    background: var(--md-surface-container-low);
}

.upload-row__name {
    font-size: 12px;
    color: var(--md-on-surface-variant);
    overflow-wrap: anywhere;
}

.upload-row__file::file-selector-button:hover {
    background: var(--md-surface-container-high);
}

@media (max-width: 900px) {
    .upload-row {
        flex-wrap: wrap;
    }
}

@media (max-width: 900px) {
    .role-seat,
    .role-seat__form {
        flex-wrap: wrap;
    }
}

.form-advanced__grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 24px;
}

@media (max-width: 900px) {
    .form-advanced__grid {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 900px) {
    .form-grid {
        grid-template-columns: 1fr;
    }
}

.title {
    font-size: 22px;
    line-height: 28px;
    font-weight: 500;
    color: var(--md-on-surface);
}

.title--sub {
    font-size: 16px;
    line-height: 24px;
    font-weight: 500;
}

.note {
    font-size: 14px;
    color: var(--md-on-error-container);
    background: var(--md-error-container);
    border-radius: var(--md-shape-sm);
    padding: 12px 16px;
}

.note--success {
    color: var(--md-on-success-container);
    background: var(--md-success-container);
}

/* Informational placeholders ("not set", "no runs yet") aren't errors — a
   neutral tonal surface instead of the alarming error-container red. */
.note--neutral {
    display: inline-block;
    color: var(--md-on-surface-variant);
    background: var(--md-surface-container-high);
    border-radius: var(--md-shape-sm);
    padding: 12px 16px;
}

/* A real, near-certain problem the operator can still fix before running
   (e.g. a chain step with no task at all) — more urgent than --neutral, but
   this isn't a hard error the server rejected, so not error-container red. */
.note--warning {
    display: inline-block;
    color: var(--md-on-warning-container);
    background: var(--md-warning-container);
    border-radius: var(--md-shape-sm);
    padding: 12px 16px;
}

/* A note that lists things — one line per node a graph does not feed. The
   markers sit inside the padding rather than hanging outside the tonal
   surface. */
.note__list {
    margin: 8px 0 0 20px;
}

.note__list li {
    margin-top: 4px;
}

/* A titled section inside a form — the project settings' Code index:
   heading, an abstract of the guide chapter, the stored index's card and
   the one switch, kept tighter than the sheet's 20px row gap so the block
   reads as one thing between the field grid and Save. The top rule is
   the one .form-grid > .form-advanced draws: the same "a section inside
   a form" boundary. */
.form-section {
    display: flex;
    flex-direction: column;
    row-gap: 12px;
    margin-top: 8px;
    border-top: 1px solid var(--md-outline-variant);
    padding-top: 16px;
}

.form-section > .title {
    margin: 0;
}

/* The stored index's card: a .note wearing the state's tone. The surface
   says the state in colour and the leading word says it in text; under
   the verdict, two fact lines set the stored snapshot against this
   binary. Block, not the inline-block of the neutral and warning tones —
   it is a row of the section, full width like the hint above it. */
.index-card {
    display: block;
}

.index-card p {
    margin: 0;
}

.index-card p + p {
    margin-top: 6px;
}

.index-card__verdict {
    font-size: 14px;
}

.index-card__facts {
    font-size: 13px;
}

/* The snapshot's facts as a two-column list: the label in the muted
   colour, the value beside it, one row per fact the row carries. */
.index-facts {
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 12px;
    row-gap: 2px;
}

.index-facts dt {
    color: var(--md-on-surface-variant);
}

.index-facts dd {
    margin: 0;
}

.index-card code {
    font-family: var(--md-font-mono);
    font-size: 12px;
}

/* The build door under the card: the button, then its one-sentence hint
   (or the running build's link, or the missing repository's cure), then
   the last build's line — a short stack, tighter than the page's section
   gap. */
.index-build {
    display: flex;
    flex-direction: column;
    row-gap: 8px;
    margin: 12px 0 8px;
}

.index-build p {
    margin: 0;
}

.login-wrapper {
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background: var(--md-background);
}

.login-card {
    background: var(--md-surface-container-lowest);
    border-radius: var(--md-shape-xl);
    padding: 40px;
    box-shadow: var(--md-elevation-2);
}

/* The second way in, when the deployment offers one: a rule-word between
   the password form's button and Google's, and Google's pill outlined so
   the form's filled Sign in stays the primary action. */
.login-card__or {
    margin: -8px 0;
    text-align: center;
    font-size: 13px;
    color: var(--md-on-surface-variant);
}

.login-card__google {
    background: transparent;
    border: 1px solid var(--md-outline);
    color: var(--md-primary);
}

.login-card__google:hover {
    background: color-mix(in srgb, var(--md-primary) 8%, transparent);
    box-shadow: none;
}

/* A quiet line on a sign-in card — what to expect after the form, or
   what the Google door does — and the other doors at the foot of the
   card. The console has three sign-in-shaped pages on one card — the
   operator's sign-in, the user's sign-in and the user's registration —
   and a person handed the console's address is as likely to be at the
   wrong one as the right one, so every card links the others. Links here
   are drawn as links: index.css inherits colour and drops the underline
   everywhere else, which is right for a row link in a table and wrong for
   the one sentence that leads somewhere else. */
.login-card__lead {
    margin: -8px 0 0;
    font-size: 13px;
    line-height: 20px;
    color: var(--md-on-surface-variant);
}

.login-card__links {
    margin: 4px 0 0;
    padding-top: 16px;
    border-top: 1px solid var(--md-outline-variant);
    font-size: 13px;
    line-height: 20px;
    color: var(--md-on-surface-variant);
}

.login-card__lead a,
.login-card__links a {
    color: var(--md-primary);
    text-decoration: underline;
}

.back-link {
    font-size: 14px;
    font-weight: 500;
    color: var(--md-primary);
}

/* Shared utility: content only assistive technology reads. It lives here
   rather than beside one component because the runs pages need it too —
   a link that opens a new tab has to say so to someone who cannot see it
   happen. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}
