﻿/* BlockReady, built on tokens.css. No literal colors or sizes here — if a value is missing from the
 * token set, add it there rather than writing it in place.
 *
 * The system is flat: hairline rules separate things, nothing is elevated, and the only place that
 * drops every softness is the offline strip, which has to be unmissable.
 */

/* ---- Base ---------------------------------------------------------------------------------- */

html {
    font-size: 16px;
}

body {
    /* The footer belongs at the bottom of the window on a short page, not floating under three
       lines of text. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    margin: 0;
    font-family: var(--br-font);
    font-size: var(--br-text-base);
    line-height: var(--br-leading-body);
    color: var(--br-ink);
    background: var(--br-ground);
    -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
    font-weight: 800;
    letter-spacing: -0.01em;
    text-wrap: balance;
}

h1 {
    font-size: var(--br-text-h1);
    letter-spacing: var(--br-tracking-display);
    line-height: var(--br-leading-tight);
}

h2 {
    font-size: var(--br-text-h2);
}

p,
li {
    max-width: var(--br-measure);
}

a {
    color: var(--br-emerald);
    text-underline-offset: 2px;
}

a:hover {
    color: var(--br-emerald-hover);
}

/* One focus treatment for everything, always visible. A neighbor tabbing through the intake form on
   a laptop has to be able to see where they are, and :focus-visible keeps it off mouse clicks.
 *
 * The component selectors are not redundant. Bootstrap ships `.btn:focus-visible { outline: 0 }` and
 * `.form-control:focus { outline: 0; box-shadow: 0 0 0 .25rem rgba(13,110,253,.25) }` at specificity
 * 0,2,0, which beats a bare `:focus-visible` at 0,1,0 — so without these the ring below never
 * appeared on a single button or text field, and a Bootstrap-blue glow appeared instead: the exact
 * accent this system refuses, on the exact interaction that matters most for accessibility. */
:focus-visible,
.btn:focus-visible,
.form-control:focus,
.form-select:focus,
.form-check-input:focus {
    outline: 3px solid var(--br-emerald);
    outline-offset: 2px;
    border-radius: var(--br-radius-sm);
    box-shadow: none;
}

/* Nothing in this system is raised, including while it is being pressed. */
.btn {
    --bs-btn-focus-box-shadow: none;
    --bs-btn-active-shadow: none;
}

/* On the pine field, emerald-on-pine computes to 2.24:1 — under the 3:1 WCAG floor for focus
   appearance, so keyboard focus on the wordmark or the sign-out button would be invisible. */
.br-masthead :focus-visible,
.br-offline :focus-visible {
    outline-color: var(--br-emerald-bright);
}

/* `hidden` has to win against a class that sets display. The offline strip is display:flex and
   starts hidden; today only Bootstrap's reboot keeps it down, which makes offline behaviour depend
   on a vendor stylesheet loading. */
[hidden] {
    display: none !important;
}

/* Paper-only content. This lives here rather than in print.css, where a `display: none` inside a
   stylesheet linked media="print" only ever applies while printing — which is precisely when it
   must not. */
.print-only {
    display: none;
}

/* Keyboard users land here first; it stays out of the way until it is focused. */
.br-skip {
    position: absolute;
    left: -9999px;
    top: var(--br-space-2);
    z-index: var(--br-z-skip);
    padding: var(--br-space-3) var(--br-space-4);
    font-weight: 600;
    color: var(--br-ink);
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius);
}

.br-skip:focus {
    left: var(--br-space-4);
}

/* ---- Masthead ------------------------------------------------------------------------------ */

.br-masthead {
    background: var(--br-pine);
    color: var(--br-ink-inverse);
}

.br-masthead__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--br-space-4);
    min-height: 76px;
    padding: var(--br-space-3) 0;
}

.br-masthead__brand {
    display: inline-flex;
    align-items: center;
    gap: var(--br-space-3);
    font-size: var(--br-text-brand);
    letter-spacing: -0.02em;
    color: var(--br-ink-inverse);
    text-decoration: none;
}

.br-masthead__brand:hover {
    color: var(--br-ink-inverse);
}

/* The mark is sized by height alone and lets its own aspect ratio pick the width — it is wider
   than it is tall, and pinning both would squash it. 28px is the floor: below that the runner and
   the ring close up into a blob. Rendered and checked, not assumed.

   The mark's structure is currentColor and follows this rule's color. Its two accents are bound
   here rather than written into the markup, so the palette stays the only place they are spelled. */
.br-masthead__mark {
    height: 30px;
    width: auto;
    flex-shrink: 0;
    color: var(--br-ink-inverse);
    --tr-mark-arrow: var(--br-mark-orange);
    --tr-mark-cross: var(--br-mark-blue);
}

/* House name regular, product name 800. The weight difference IS the hierarchy — it is what makes
   one lockup read as "BlockReady, by TrainedResponder" rather than as two products jammed together.
   Never set both the same.

   There is no separator between them. A colon was tried and dropped: the weight change already
   divides the two names, and a colon divides them a second time. The gap below is the whole join —
   it rides on the font size, so it holds at any scale. */
.br-masthead__wordmark {
    white-space: nowrap;
    line-height: 1;
}

.br-masthead__house {
    font-weight: 500;
    color: var(--br-pine-ink);
    padding-right: 0.34em;
}

.br-masthead__product {
    font-weight: 800;
    color: var(--br-ink-inverse);
}

.br-masthead__meta {
    display: flex;
    align-items: center;
    gap: var(--br-space-3);
    font-size: var(--br-text-sm);
    color: var(--br-pine-ink);
}

.br-masthead__who {
    color: var(--br-pine-ink);
}

/* Controls sitting on the pine field need their own treatment: the page's outline button is dark
   ink on a light sheet, which on this background is invisible. */
.br-masthead .btn-outline-secondary {
    --bs-btn-color: var(--br-ink-inverse);
    --bs-btn-border-color: var(--br-pine-line);
    --bs-btn-hover-color: var(--br-pine);
    --bs-btn-hover-bg: var(--br-ink-inverse);
    --bs-btn-hover-border-color: var(--br-ink-inverse);
}

.br-masthead form {
    margin: 0;
}

/* ---- The menu ------------------------------------------------------------------------------
   A <details> on the pine field. The panel is a white sheet hanging under the masthead: separated by
   a hairline and a radius, never by a shadow, because nothing in this system is raised.

   Right-aligned and sized to its content at every width rather than going full-bleed on a phone. A
   panel that pushed the page down would move the content under a thumb already travelling towards
   it, and at 390 a right-aligned panel is the thumb-reachable corner anyway. */

.br-menu {
    position: relative;
}

.br-menu__button {
    display: inline-flex;
    align-items: center;
    gap: var(--br-space-2);
    min-height: var(--br-control-height-sm);
    padding: 0 var(--br-space-4);
    font-size: var(--br-text-sm);
    font-weight: 600;
    color: var(--br-ink-inverse);
    background: transparent;
    border: var(--br-border) solid var(--br-pine-line);
    border-radius: var(--br-radius-sm);
    cursor: pointer;

    /* The disclosure triangle is the browser's, and it is not this system's. Both properties are
       needed: the standard one, and WebKit's, which ignores it. */
    list-style: none;
}

.br-menu__button::-webkit-details-marker {
    display: none;
}

.br-menu__button:hover,
.br-menu[open] > .br-menu__button {
    color: var(--br-pine);
    background: var(--br-ink-inverse);
    border-color: var(--br-ink-inverse);
}

.br-menu__panel {
    position: absolute;
    top: calc(100% + var(--br-space-2));
    right: 0;
    z-index: var(--br-z-menu);
    min-width: 15rem;

    /* Never wider than the screen it hangs in. The gutter is the container's own below 576px. */
    max-width: calc(100vw - var(--br-space-5) * 2);
    padding: var(--br-space-2) 0;
    text-align: left;
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius-lg);
}

.br-menu__section + .br-menu__section {
    margin-top: var(--br-space-2);
    padding-top: var(--br-space-2);
    border-top: var(--br-border) solid var(--br-line);
}

.br-menu__heading {
    margin: 0;
    padding: var(--br-space-2) var(--br-space-4);
    max-width: none;
    font-size: var(--br-text-sm);
    font-weight: 700;
    color: var(--br-ink);
}

.br-menu__list {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* The measure is for prose. Without this every row clamps at 56ch and the panel collapses to a
   column of wrapped link text — the trap .br-list already documents. */
.br-menu__list > li {
    max-width: none;
}

.br-menu__list a,
.br-menu__action {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: var(--br-touch-min);
    padding: 0 var(--br-space-4);
    font-size: var(--br-text-base);
    color: var(--br-ink);
    text-align: left;
    text-decoration: none;
    background: none;
    border: 0;
    cursor: pointer;
}

.br-menu__list a:hover,
.br-menu__action:hover {
    color: var(--br-ink);
    background: var(--br-sheet-alt);
}

/* Where you are standing. A weight change and a rule down the inside edge, rather than a fill: the
   menu is a list of places, and one of them being shaded reads as selected-for-an-action. */
.br-menu__list a[aria-current] {
    font-weight: 700;

    /* A border and not an inset shadow: nothing in this system is raised, and the Flat-Forever Rule
       does not make an exception for a shadow being used as a line. The padding gives back what the
       border takes, so the label does not shift as you move between rows. */
    border-left: var(--br-rail) solid var(--br-emerald);
    padding-left: calc(var(--br-space-4) - var(--br-rail));
}

.br-menu__who {
    margin: 0;
    padding: var(--br-space-2) var(--br-space-4);
    max-width: none;
    font-size: var(--br-text-sm);
    color: var(--br-ink-muted);
    overflow-wrap: anywhere;
}

/* The panel is inside .br-masthead, so it inherits the pine field's bright-emerald focus ring — which
   is the one that fails contrast on white. Put it back. */
.br-masthead .br-menu__panel :focus-visible {
    outline-color: var(--br-emerald);
}

/* ---- The context bar ------------------------------------------------------------------------
   Where you are, and what you are here. The quiet band: it sits under a pine masthead and sometimes
   under two coloured strips as well, so it earns its place by being almost nothing — a white strip
   with a hairline under it. */

.br-trail {
    background: var(--br-sheet);
    border-bottom: var(--br-border) solid var(--br-line);
}

.br-trail__inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--br-space-3);
    min-height: var(--br-touch-min);
    padding: var(--br-space-2) 0;
}

.br-trail__steps ol {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--br-space-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.br-trail__steps li {
    display: flex;
    align-items: center;
    gap: var(--br-space-2);
    max-width: none;
    font-size: var(--br-text-sm);
}

/* The separator belongs to the step it precedes, so it never strands on its own line when the trail
   wraps on a phone. */
.br-trail__steps li + li::before {
    /* The literal character, not a CSS escape. An escape runs until a non-hex character,
       so the four-digit form is read as U+0203 followed by a capital A. This file is UTF-8
       and served as UTF-8; the character is the unambiguous way to write it. */
    content: "›";
    color: var(--br-ink-faint);
}

.br-trail__steps a {
    color: var(--br-ink-muted);
    text-decoration: none;
}

.br-trail__steps a:hover {
    color: var(--br-ink);
    text-decoration: underline;
}

.br-trail__steps [aria-current] {
    font-weight: 600;
    color: var(--br-ink);
}

.br-trail__standing {
    flex-shrink: 0;
}

/* ---- The neighbor's-eye strip -------------------------------------------------------------
   An organizer standing where their neighbors stand. Amber rather than pine, because it is not the
   ordinary state and the whole job of this bar is to be remembered: the failure it prevents is an
   organizer who forgot they were in it. Same flat, radius-free shape as the offline strip below —
   the two never appear together often, but when they do they should read as one family. */

.br-preview {
    padding: var(--br-space-3) 0;
    font-size: var(--br-text-base);
    color: var(--br-ink);
    background: var(--br-amber-signal);
}

.br-preview__dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--br-pine-deep);
}

.br-preview strong {
    font-weight: 600;
}

/* Pushed to the end of the row at desk width, and allowed to fall in line on a phone rather than
   being squeezed: an organizer works standing in the street, and the way out has to stay tappable. */
.br-preview__back {
    margin-left: auto;
}

/* At 390 the row becomes a stack, and a stack of three full-size lines pushes the page itself below
   the fold — which would make the strip the thing you scroll past rather than the thing you read.
   The dot goes first: on the offline strip its color carries meaning, and here the amber bar has
   already said everything the dot would. */
@media (max-width: 575.98px) {
    .br-preview {
        padding: var(--br-space-2) 0;
    }

    .br-preview__inner {
        gap: var(--br-space-2) !important;
    }

    .br-preview__dot {
        display: none;
    }

    .br-preview__note {
        font-size: var(--br-text-sm);
    }

    .br-preview__back {
        margin-left: 0;
    }
}

/* ---- The offline strip --------------------------------------------------------------------
   The one element with no warmth, no radius and no softness. It is the thing a neighbor needs to
   understand in half a second on the worst day of the year. */

.br-offline {
    display: flex;
    align-items: center;
    gap: var(--br-space-3);
    padding: var(--br-space-3) 0;
    font-size: var(--br-text-base);
    color: var(--br-ink-inverse);
    background: var(--br-pine-deep);
}

.br-offline__dot {
    flex-shrink: 0;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--br-amber-signal);
}

.br-offline strong {
    font-weight: 600;
}

main {
    flex-grow: 1;
}

/* Standalone instruction text — how to get a new link, who to ask for an invitation. It is the
   recovery path, so it stays at body size. `.form-text` is for helper text under a field and is
   both smaller and muted; using it here shrank the sentences a stuck neighbor needs most. */
.br-note {
    font-size: var(--br-text-base);
    color: var(--br-ink-muted);
}

/* ---- Page furniture ------------------------------------------------------------------------ */

main {
    padding-top: var(--br-space-6);
}

/* Single-column pages — signing in, an invitation, an error. Held narrow because they are read
   once, quickly, often on a phone. */
.br-narrow {
    max-width: 34rem;
    margin-inline: auto;
}

/* Forms that are filled in rather than read: intake, a household, a neighborhood's details. Wider
   than .br-narrow because their rows pair fields side by side and 34rem squeezes a first and last
   name into slivers — but bounded, because a text input eleven hundred pixels wide invites nobody
   to type four characters into it, and the eye loses the label on the way back.

   This class was used by the household forms before it existed, so until now they ran the full
   width of the window. Designing at 390 is not an excuse for never opening a laptop. */
.br-narrow-form {
    max-width: 48rem;
    margin-inline: auto;
}

.br-page-head {
    margin-bottom: var(--br-space-5);
}

.br-page-head h1 {
    margin: 0;
    font-size: var(--br-text-display);
}

/* Display size belongs to a page whose subject is a place — a neighborhood's own name. On a
   one-column utility page it is just shouting at somebody trying to sign in. */
.br-narrow .br-page-head h1 {
    font-size: var(--br-text-h1);
}

/* The line under a page title: who you are here, how many households, which block. */
.br-lede {
    margin: var(--br-space-2) 0 0;
    font-size: var(--br-text-lg);
    color: var(--br-ink-muted);
}

.br-lede strong {
    font-weight: 600;
    color: var(--br-ink);
}

/* A stack of sheet rows that are also links — the list of neighborhoods you belong to. */
.br-list {
    margin: 0;
    padding: 0;
    list-style: none;
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius-lg);
    overflow: hidden;
}

.br-list > li + li {
    border-top: var(--br-border) solid var(--br-line);
}

.br-list a {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--br-space-3);
    min-height: var(--br-touch-min);
    padding: var(--br-space-4) var(--br-space-5);
    font-weight: 600;
    color: var(--br-ink);
    text-decoration: none;
}

.br-list a:hover {
    background: var(--br-sheet-alt);
}

/* The reading measure on `li` is right for a bulleted paragraph and wrong for a row of layout: it
   was capping these rows at 56ch inside a card three times that wide, so a row stopped halfway
   across and left dead space beside it. A row in a list of links is not prose. */
.br-list > li {
    max-width: none;
}

/* Rows whose second element EXPLAINS the first rather than badging it. Plain .br-list rows end in a
   stamp, and pushing that to the far edge is correct; a sentence pushed to the far edge is a
   sentence nobody connects to the word it belongs to. Here the name holds a column, so the
   explanations line up and each row reads as a term and its definition. */
.br-list--described a {
    justify-content: flex-start;
    align-items: baseline;
}

.br-list--described a > :first-child {
    flex: 0 0 auto;
    min-width: 9rem;
}

.br-list--described .br-note {
    flex: 1 1 auto;
}

/* ---- The two doors ------------------------------------------------------------------------
   A neighbor's landing: what you told the block, and what the block told you. Two of them, never
   three, so neither has to compete — and side by side at desk width, stacked on a phone, because at
   390 two cards in a row are two cards nobody can read.

   Not built on .br-list: a row in a list is a label and a value, and these are a question and its
   current answer. The answer is the biggest thing in the card, which is the whole point of landing
   here rather than on a menu. */

.br-doors {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--br-space-4);
}

.br-door {
    display: flex;
    flex-direction: column;
    padding: var(--br-space-5);
    color: var(--br-ink);
    text-decoration: none;
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius-lg);
}

.br-door:hover {
    color: var(--br-ink);
    background: var(--br-sheet-alt);
    border-color: var(--br-line-strong);
}

.br-door h2 {
    margin: 0;
    font-size: var(--br-text-h2);
}

/* The answer, and the reason the card exists. Sized between h2 and the display face: it has to beat
   its own heading without shouting across at the page title. */
.br-door__state {
    margin: var(--br-space-3) 0 0;
    max-width: none;
    font-size: var(--br-text-h1);
    font-weight: 800;
    line-height: var(--br-leading-tight);
    letter-spacing: -0.02em;
}

/* Nothing recorded is a state, not a failure. Muted rather than red: nobody is late. */
.br-door__state--empty {
    color: var(--br-ink-muted);
    font-weight: 600;
}

.br-door__detail {
    margin: var(--br-space-2) 0 0;
    max-width: none;
    color: var(--br-ink-muted);
}

/* Pushed to the bottom so two cards of different heights still line their actions up. */
.br-door__go {
    margin-top: auto;
    padding-top: var(--br-space-4);
    font-weight: 600;
    color: var(--br-emerald);
}

.br-door:hover .br-door__go {
    text-decoration: underline;
}

/* ---- List rows that carry state -------------------------------------------------------------
   The neighborhood chooser. A modifier on .br-list rather than a second component: the row is still
   a link to a place, it just says what is going on there before you go. */

.br-list--stated .br-list__what {
    display: flex;
    flex-direction: column;
    gap: var(--br-space-1);
}

.br-list--stated .br-list__name {
    font-weight: 600;
}

.br-list--stated .br-list__state {
    font-size: var(--br-text-sm);
    font-weight: 400;
    color: var(--br-ink-muted);
}

/* ---- Sheets -------------------------------------------------------------------------------- */

.br-sheet {
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius-lg);
}

.br-sheet__body {
    padding: var(--br-space-5);
}

/* The one inverted panel. Used for the thing a coordinator glances at rather than reads. */
.br-panel-pine {
    padding: var(--br-space-5);
    color: var(--br-ink-inverse);
    background: var(--br-pine-panel);
    border-radius: var(--br-radius-lg);
}

.br-panel-pine h2 {
    color: var(--br-ink-inverse);
}

.br-panel-pine a {
    color: var(--br-emerald-bright);
}

/* ---- Tally: an inventory sheet, not a row of hero numbers ---------------------------------- */

.br-tally {
    margin: 0;
    padding: 0;
    list-style: none;
}

.br-tally li {
    display: flex;
    align-items: baseline;
    gap: var(--br-space-2);
    padding: var(--br-space-1) 0;
}

.br-tally__leader {
    flex-grow: 1;
    border-bottom: 1px dotted var(--br-pine-leader);
}

.br-tally__count {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* ---- Buttons -------------------------------------------------------------------------------
   Flat. The primary is the only saturated element on most screens, and there is one to a page. */

.btn {
    --bs-btn-border-radius: var(--br-radius);
    min-height: var(--br-control-height);
    /* Real vertical padding, not just a min-height with flex centering: a label that wraps on a
       narrow screen would otherwise sit flush against the button's own edge. */
    padding: var(--br-space-3) var(--br-space-5);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--br-space-2);
    font-family: var(--br-font);
    font-size: var(--br-text-base);
    font-weight: 600;
    border-radius: var(--br-radius);
    border-width: var(--br-border);
}

.btn-primary {
    --bs-btn-bg: var(--br-emerald);
    --bs-btn-border-color: var(--br-emerald);
    --bs-btn-hover-bg: var(--br-emerald-hover);
    --bs-btn-hover-border-color: var(--br-emerald-hover);
    --bs-btn-active-bg: var(--br-emerald-hover);
    --bs-btn-disabled-bg: var(--br-emerald);
    font-weight: 700;
}

.btn-outline-secondary {
    --bs-btn-color: var(--br-ink);
    --bs-btn-border-color: var(--br-line-strong);
    --bs-btn-hover-color: var(--br-ink);
    --bs-btn-hover-bg: var(--br-sheet-alt);
    --bs-btn-hover-border-color: var(--br-ink-faint);
}

.btn-danger {
    --bs-btn-bg: var(--br-red);
    --bs-btn-border-color: var(--br-red);
    --bs-btn-hover-bg: var(--br-red-hover);
    --bs-btn-hover-border-color: var(--br-red-hover);
    font-weight: 700;
}

.btn-link {
    --bs-btn-color: var(--br-emerald);
    --bs-btn-hover-color: var(--br-emerald-hover);
    font-weight: 600;
}

.btn-sm {
    min-height: var(--br-control-height-sm);
    padding: var(--br-space-2) var(--br-space-3);
    font-size: var(--br-text-sm);
}

/* Separates two routes to the same place — an emailed link and a provider — so neither reads as the
   fallback for the other. */
.br-or {
    display: flex;
    align-items: center;
    gap: var(--br-space-3);
    margin: var(--br-space-4) 0;
    font-size: var(--br-text-sm);
    color: var(--br-ink-muted);
}

.br-or::before,
.br-or::after {
    content: "";
    flex-grow: 1;
    border-top: var(--br-border) solid var(--br-line);
}

/* ---- Forms ---------------------------------------------------------------------------------
   Most neighbors see the intake form once and never come back to it. Labels above, help below,
   errors that name the fix. */

.form-label {
    margin-bottom: var(--br-space-1);
    font-size: var(--br-text-label);
    font-weight: 600;
}

.form-control,
.form-select {
    min-height: var(--br-control-height);
    padding: var(--br-space-2) var(--br-space-4);
    font-family: var(--br-font);
    font-size: var(--br-text-base);
    color: var(--br-ink);
    background-color: var(--br-sheet-alt);
    border: var(--br-border) solid var(--br-line-strong);
    border-radius: var(--br-radius);
}

.form-control:focus,
.form-select:focus {
    border-color: var(--br-emerald);
    background-color: var(--br-sheet);
    box-shadow: none;
}

.form-control:disabled,
.form-select:disabled {
    color: var(--br-ink-muted);
    background-color: var(--br-ground);
}

textarea.form-control {
    min-height: calc(var(--br-control-height) * 2);
    line-height: var(--br-leading-body);
}

.form-text {
    font-size: var(--br-text-sm);
    color: var(--br-ink-muted);
}

/* Native validation styling is replaced wholesale: Bootstrap's icon-in-the-field treatment is
   invisible to anyone who does not already know what it means. */
.form-control.is-invalid,
.form-select.is-invalid {
    border-color: var(--br-red);
    background-image: none;
}

.text-danger,
.validation-summary-errors,
.field-validation-error {
    font-size: var(--br-text-sm);
    font-weight: 600;
    color: var(--br-red) !important;
}

.form-check-input {
    width: 1.375rem;
    height: 1.375rem;
    margin-top: 0.125rem;
    border: var(--br-border) solid var(--br-line-strong);
}

.form-check-input:checked {
    background-color: var(--br-emerald);
    border-color: var(--br-emerald);
}

.form-check-label {
    display: flex;
    align-items: center;
    min-height: var(--br-touch-min);
    margin-left: var(--br-space-2);
}

fieldset legend {
    font-size: var(--br-text-label);
    font-weight: 600;
}

/* ---- Tables --------------------------------------------------------------------------------
   The roster reads like a ruled sheet: heavy rule under the head, hairlines between rows. */

.table {
    --bs-table-bg: transparent;
    --bs-table-striped-bg: var(--br-sheet-alt);
    margin: 0;
    color: var(--br-ink);
}

.table > thead th {
    padding: var(--br-space-2) var(--br-space-3);
    font-size: var(--br-text-xs);
    font-weight: 700;
    letter-spacing: var(--br-tracking-label);
    text-transform: uppercase;
    color: var(--br-ink-muted);
    border-bottom: 2px solid var(--br-ink);
}

.table > tbody td {
    padding: var(--br-space-3);
    border-bottom: var(--br-border) solid var(--br-line);
    vertical-align: top;
}

.table > tbody tr:last-child td {
    border-bottom: none;
}

/* A table inside a sheet keeps the sheet's own margins, so its first column lines up with the
   heading above it instead of sitting 10px to the left of everything else. */
.br-sheet .table > * > tr > *:first-child {
    padding-left: var(--br-space-5);
}

.br-sheet .table > * > tr > *:last-child {
    padding-right: var(--br-space-5);
}

/* A table that cannot fit scrolls inside its sheet rather than pushing the whole page sideways;
   without this the layout, the masthead and the footer all go with it. */
.table-responsive {
    border-top: var(--br-border) solid var(--br-line);
}

.table-responsive > .table > * > tr > *:first-child {
    padding-left: var(--br-space-5);
}

.table-responsive > .table > * > tr > *:last-child {
    padding-right: var(--br-space-5);
}

/* An access code exists to be read aloud from a printed sheet and typed into a phone, so it is set
   large, spaced, and in the one place the system uses tabular figures. */
.br-code {
    display: inline-block;
    margin: 0 0 var(--br-space-2);
    padding: var(--br-space-2) var(--br-space-4);
    font-size: var(--br-text-h1);
    font-weight: 800;
    letter-spacing: 0.12em;
    font-variant-numeric: tabular-nums;
    color: var(--br-ink);
    background: var(--br-sheet-alt);
    border: var(--br-border) solid var(--br-line-strong);
    border-radius: var(--br-radius);
}


/* The box the code is typed INTO, which wants the same treatment as the box it is read FROM: large,
   spaced, tabular. Not monospace as a costume — this is a code being entered character by character,
   which is the case that actually earns even spacing. */
.br-code-input {
    font-size: var(--br-text-h2);
    font-weight: 700;
    letter-spacing: 0.12em;
    font-variant-numeric: tabular-nums;
    text-transform: uppercase;
}

.br-code-input::placeholder {
    font-weight: 400;
    letter-spacing: 0.08em;
    text-transform: none;
}

/* A page that fills the screen instead of sitting in the board's column — the map, and so far only
   the map. The layout swaps this in for .container, so there is nothing to undo. */
/* A page that fills the screen instead of sitting in the board's column — the map, and so far only
   the map. The layout swaps this in for .container, so there is nothing to undo.
 *
 * A flex column rather than a height in vh: body is already one, main already grows into it, and
 * measuring "the screen minus the masthead" by hand means being wrong every time something else is
 * there — the offline strip, a masthead that wrapped, the footer. This asks for what is left. */
.br-main--bleed {
    display: flex;
    flex-direction: column;

    /* The column's own breathing room belongs to the column. A full-bleed page starts at the
       masthead, and 2rem of board above the map is a gap that reads as a mistake. */
    padding: 0;

    /* A flex item will not shrink below its content without this, and the map's content is a list
       as long as the block. */
    min-height: 0;
}

/* ---- Chips: what people can do, what they own, what is private ----------------------------- */

.br-chip {
    display: inline-block;
    padding: 0.2rem 0.6rem;
    margin: 0.1rem 0.15rem 0.1rem 0;
    font-size: var(--br-text-xs);
    font-weight: 600;
    border-radius: var(--br-radius-sm);
    color: var(--br-slate-ink);
    background: var(--br-slate-tint);
}

.br-chip--has {
    color: var(--br-emerald-ink);
    background: var(--br-emerald-tint);
}

.br-chip--private {
    display: inline-flex;
    align-items: center;
    gap: var(--br-space-1);
    color: var(--br-amber-ink);
    background: var(--br-amber-tint);
}

.br-chip--none {
    color: var(--br-ink-muted);
    background: var(--br-ground);
}

/* ---- Stamps: status, in the grammar of a rubber stamp -------------------------------------- */

.br-stamp {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    font-size: var(--br-text-xs);
    font-weight: 800;
    letter-spacing: 0.1em;
    border: 2px solid currentColor;
    border-radius: var(--br-radius-sm);
}

.br-stamp--ready {
    color: var(--br-emerald);
}

.br-stamp--pending {
    color: var(--br-amber-ink);
    border-style: dashed;
}

.br-stamp--urgent {
    color: var(--br-red);
}

.br-stamp--none {
    color: var(--br-ink-muted);
}

/* ---- Messages ------------------------------------------------------------------------------ */

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--br-space-3);
    padding: var(--br-space-4);
    font-size: var(--br-text-lg);
    border-width: var(--br-border);
    border-radius: var(--br-radius);
}

.alert-success {
    --bs-alert-color: var(--br-emerald-ink);
    --bs-alert-bg: var(--br-emerald-tint);
    --bs-alert-border-color: var(--br-emerald);
}

.alert-info {
    --bs-alert-color: var(--br-slate-ink);
    --bs-alert-bg: var(--br-slate-tint);
    --bs-alert-border-color: var(--br-slate-ink);
}

.alert-warning {
    --bs-alert-color: var(--br-amber-ink);
    --bs-alert-bg: var(--br-amber-tint);
    --bs-alert-border-color: var(--br-amber);
}

.alert-danger {
    --bs-alert-color: var(--br-red-ink);
    --bs-alert-bg: var(--br-red-tint);
    --bs-alert-border-color: var(--br-red-line);
}

/* ---- Tabs: the active one IS the sheet ----------------------------------------------------- */

.br-tabs {
    display: flex;
    align-items: flex-end;
    gap: var(--br-space-1);
    margin-bottom: -1px;
}

.br-tabs a,
.br-tabs span {
    display: flex;
    align-items: center;
    min-height: var(--br-touch-min);
    padding: 0 var(--br-space-5);
    font-size: var(--br-text-base);
    font-weight: 600;
    color: var(--br-ink-muted);
    background: var(--br-line);
    text-decoration: none;
}

.br-tabs [aria-current] {
    font-weight: 700;
    color: var(--br-ink);
    background: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-bottom: none;
}

/* ---- Empty states -------------------------------------------------------------------------- */

.br-empty {
    padding: var(--br-space-7) var(--br-space-6);
    text-align: center;
}

/* The empty-state drawing takes its colors from the token set like everything else — an inline
   SVG with hexes baked in is the quickest way for a palette change to leave one thing behind. */
.br-mark-empty .board { fill: var(--br-ground); stroke: var(--br-line-strong); }
.br-mark-empty .sheet { fill: var(--br-sheet); stroke: var(--br-line-strong); }
.br-mark-empty .pin { fill: var(--br-emerald); }

.br-empty p {
    max-width: 40ch;
    margin-inline: auto;
    color: var(--br-ink-muted);
}

/* ---- Footer -------------------------------------------------------------------------------- */

.br-footer {
    margin-top: auto;
    padding: var(--br-space-4) 0;
    font-size: var(--br-text-sm);
    color: var(--br-ink-muted);
    background: var(--br-sheet);
    border-top: var(--br-border) solid var(--br-line);
}

/* ---- Motion --------------------------------------------------------------------------------
   Almost none, and none of it load-bearing. Somebody who has set reduced motion gets none at all. */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

/* ---- Intake form -------------------------------------------------------------------------- */

/* One person's block on the household form. A hairline box rather than a heading and a rule,
   because on a phone the fields of two people run together into one long column otherwise. */
.br-person {
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius);
    padding: var(--br-space-4);
    margin-top: var(--br-space-4);
}

/* A legend is float-y and unstyleable by default; this makes it an ordinary label above the box. */
.br-person__legend {
    float: none;
    width: auto;
    padding: 0;
    margin-bottom: var(--br-space-3);
    font-size: var(--br-text-sm);
    font-weight: 700;
    letter-spacing: var(--br-tracking-label);
    text-transform: uppercase;
    color: var(--br-ink-muted);
}

/* A disclosure used as a two-step confirmation. Sized to the touch floor, because it is the only
   way to reach a destructive action. */
.br-summary {
    display: flex;
    align-items: center;
    min-height: var(--br-touch-min);
    font-weight: 600;
    color: var(--br-red-ink);
    cursor: pointer;
}

/* A list that is read, not tapped — the buildings on a site. Not .br-list, which is rows of links. */
.br-plain-list {
    margin: var(--br-space-4) 0 0;
    padding: 0;
    list-style: none;
    border-top: var(--br-border) solid var(--br-line);
}

.br-plain-list > li {
    display: flex;
    flex-wrap: wrap;
    gap: var(--br-space-1) var(--br-space-3);
    align-items: baseline;
    padding: var(--br-space-3) 0;
    border-bottom: var(--br-border) solid var(--br-line);
}

/* ---- The roster -------------------------------------------------------------------------------
   The block as a list, and the thing the binder prints. Hairline-ruled rows on the page rather than
   a stack of cards: this is an inventory sheet in the grammar of a clipboard, and sixteen bordered
   boxes would make the page's structure louder than the block it describes. */

.br-roster-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--br-space-4);
    align-items: flex-end;
    justify-content: space-between;
    margin-bottom: var(--br-space-4);
}

.br-roster-bar__find {
    flex: 1 1 18rem;
}

.br-roster-bar__find .form-text {
    /* Reserved whether or not it says anything, so the buttons beside it do not jump when a count
       appears. */
    min-height: 1.5em;
    margin-bottom: 0;
}

.br-roster-bar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--br-space-3);
}

.br-roster-tally {
    margin-bottom: var(--br-space-2);
}

.br-roster-tally__gap {
    color: var(--br-ink-muted);
}

.br-roster-meet {
    margin-bottom: var(--br-space-4);
}

/* ONE CARD PER HOUSEHOLD, not one sheet with rules across it. A household is the unit of this whole
   product, and sixteen of them separated by a hairline read as one long column of text — the eye has
   to count line breaks to work out where one home ends and the next begins. A card is a boundary you
   do not have to look for. */
.br-roster {
    display: flex;
    flex-direction: column;
    gap: var(--br-space-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.br-roster__row {
    /* The reading measure on `li` is for prose and this is a row of layout — the same trap
       `.br-tick` and `.br-list > li` escape. */
    max-width: none;
    padding: var(--br-space-5);
    background-color: var(--br-sheet);
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius-lg);
}

/* Yours. A tint AND a stronger edge: against one white sheet the tint alone was enough, but a card
   already has an edge, so the difference has to be in that edge to read as a difference. Still no
   colour — every colour in this system means something and "yours" is not action, privacy or
   urgency. Same reasoning as the map's second ring. */
.br-roster__row--own {
    background-color: var(--br-sheet-alt);
    border-color: var(--br-line-strong);
}

.br-roster__who {
    display: flex;
    gap: var(--br-space-3);
    align-items: baseline;
}

/* The column an eye runs down when somebody is looking for one house. Tabular figures so the
   numbers line up as a column rather than as words that happen to be digits. */
.br-roster__number {
    flex: none;
    min-width: 3ch;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--br-ink-muted);
}

.br-roster__name {
    flex: 1 1 auto;
    min-width: 0;
}

.br-roster__address {
    font-size: var(--br-text-h2);
    margin-bottom: var(--br-space-1);
}

.br-roster__people {
    max-width: none;
    margin-bottom: 0;
}

.br-roster__people--none {
    color: var(--br-ink-faint);
}

.br-roster__count {
    margin-left: var(--br-space-2);
    font-size: var(--br-text-sm);
    color: var(--br-ink-muted);
    font-variant-numeric: tabular-nums;
}

/* A number somebody may be about to ring standing in the street, so it is a link and it is big
   enough to hit. */
.br-roster__phone {
    display: inline-flex;
    align-items: center;
    min-height: var(--br-touch-min);
    margin-right: var(--br-space-3);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* Term and definition, which is what these rows are. The label column is fixed so the answers line
   up down the sheet; below 576px it stacks, because a 7rem column and a chip list do not both fit
   on a phone. */
.br-roster__facts {
    display: grid;
    grid-template-columns: 7rem 1fr;
    gap: var(--br-space-2) var(--br-space-4);
    margin: var(--br-space-3) 0 0;
}

.br-roster__facts:empty {
    display: none;
}

.br-roster__facts dt {
    font-size: var(--br-text-label);
    font-weight: 700;
    letter-spacing: var(--br-tracking-label);
    text-transform: uppercase;
    color: var(--br-ink-muted);
    padding-top: 0.2rem;
}

.br-roster__facts dd {
    display: flex;
    flex-wrap: wrap;
    gap: var(--br-space-1) var(--br-space-2);
    margin: 0;
}

/* A sentence rather than a row of chips — a shutoff location and a note are prose. */
.br-roster__prose {
    display: block;
    color: var(--br-ink);
}

/* Everything an organizer may read and no neighbor may. Marked once for the whole block rather than
   once per field: six badges is the same repetition as six sentences, turned into decoration. */
.br-roster__private {
    margin-top: var(--br-space-4);
    padding-top: var(--br-space-3);
    border-top: var(--br-border) dashed var(--br-line-strong);
}

.br-roster__private-mark {
    margin-bottom: var(--br-space-2);
}

.br-roster__edit {
    display: inline-flex;
    align-items: center;
    min-height: var(--br-touch-min);
    margin-top: var(--br-space-2);
    font-weight: 600;
}

.br-roster-nothing {
    margin-top: var(--br-space-4);
    color: var(--br-ink-muted);
}

/* ---- Consents -------------------------------------------------------------------------------
   The two permissions a household grants in advance, and the only place on any form where a tick
   authorises something rather than describing it. So each one is its own hairline block rather than
   a line in a list: the weight is the point, and a row of checkboxes is what somebody ticks without
   reading. No colour is spent here — a checked box is already emerald, and the One Saturated Thing
   rule keeps that meaning for the save button. */

.br-consents {
    margin: var(--br-space-4) 0 0;
    padding: 0;
    list-style: none;
}

.br-consent {
    /* The reading measure on `li` is for prose; this is a block of layout. Same trap as .br-tick. */
    max-width: none;
    border: var(--br-border) solid var(--br-line);
    border-radius: var(--br-radius);
    padding: var(--br-space-4);
}

.br-consent + .br-consent {
    margin-top: var(--br-space-3);
}

/* Heavier than an ordinary tick label, because this one is a decision. */
.br-consent .form-check-label {
    font-weight: 600;
}

/* The condition, and the record, sit at the block's own left edge rather than hanging under the
   label. Indenting them would mean re-spelling Bootstrap's checkbox offset, and the box already
   groups them. */
.br-consent__detail,
.br-consent__record {
    max-width: none;
    margin: var(--br-space-2) 0 0;
    font-size: var(--br-text-sm);
}

.br-consent__detail {
    color: var(--br-ink-muted);
}

/* Status, not help: who decided this and when. Faint, because it is the answer to a question
   nobody asked until they needed it. */
.br-consent__record {
    color: var(--br-ink-faint);
}

/* ---- An aside inside a form block ------------------------------------------------------------
   A disclosure for fields that apply to some rows and not most — school detail on a person who is
   probably an adult. Quiet enough not to read as a section, tall enough to tap. */

.br-aside {
    margin-top: var(--br-space-3);
    border-top: var(--br-border) solid var(--br-line);
}

.br-aside__summary {
    display: flex;
    align-items: center;
    gap: var(--br-space-2);
    min-height: var(--br-touch-min);
    font-size: var(--br-text-sm);
    font-weight: 600;
    color: var(--br-ink-muted);
    cursor: pointer;
}

/* Making a summary a flex box drops its native marker, which is why the chevron is drawn in the
   markup. Both of these stop a second one appearing beside it. */
.br-aside__summary {
    list-style: none;
}

.br-aside__summary::-webkit-details-marker {
    display: none;
}

/* Points along when closed, down when open. No transition: this system has almost no motion, and a
   chevron that snaps is the honest version of a state that snaps. */
.br-aside__chevron {
    flex: none;
    color: var(--br-ink-faint);
}

.br-aside[open] .br-aside__chevron {
    transform: rotate(90deg);
}

.br-aside__summary:hover {
    color: var(--br-ink);
}

.br-aside[open] .br-aside__summary {
    color: var(--br-ink);
}

/* ---- The three lists: skills, equipment, services -------------------------------------------
   A ruled sheet of lines to tick, with room beside each one for the half of the answer that
   matters. Three lists of a dozen is the longest thing on any form in this product, so the row is
   the component: the tick and its note are one line at desk width and two on a phone, and nothing
   else is drawn around them. */

/* The consent blocks are a section in their own right, so what follows them needs the same air a
   second list gets. Without this the shutoffs heading sat flush against the last consent's border —
   and a heading with nothing above it reads as a caption on the box it is touching. */
.br-ticks + .br-ticks,
.br-consents + .br-ticks {
    margin-top: var(--br-space-6);
}

.br-ticks__head {
    font-size: var(--br-text-label);
    font-weight: 700;
    letter-spacing: var(--br-tracking-label);
    text-transform: uppercase;
    color: var(--br-ink-muted);
    margin-bottom: var(--br-space-2);
}

.br-ticks__list {
    margin: var(--br-space-3) 0 0;
    padding: 0;
    list-style: none;
    border-top: var(--br-border) solid var(--br-line);
}

.br-tick {
    /* The reading measure on `li` is for prose and this is a row of layout — the same trap
       `.br-list > li` escapes above. Left on, it capped the row at 56ch and hung the hairline in
       mid-air with the note floating short of the card's edge. */
    max-width: none;
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--br-space-4);
    align-items: center;
    padding: var(--br-space-1) 0;
    border-bottom: var(--br-border) solid var(--br-line);
}

/* Both halves ask for the same width and wrap together. Above about 34rem the note sits beside its
   line; below it, under. The basis is what decides that, not a breakpoint, so a row inside the map
   rail and a row on a full-width form each do the right thing. */
.br-tick__box,
.br-tick__note {
    flex: 1 1 14rem;
}

.br-tick__box {
    margin: 0;
}

/* The label carries the touch target, so the row needs no height of its own. */
.br-tick .form-check-label {
    font-weight: 500;
}

.br-tick__note {
    /* Quieter than a field somebody must fill in: no fill until it is focused, so a column of them
       reads as ruled lines rather than thirty empty slots demanding an answer. */
    background-color: transparent;
    border-color: transparent;
    padding-left: var(--br-space-2);
    padding-right: var(--br-space-2);
}

.br-tick__note:hover {
    border-color: var(--br-line);
}

/* ---- Narrow screens -------------------------------------------------------------------------
   Last in the file on purpose. These rules have the same specificity as the ones they override, so
   source order is the only thing that decides them — a media query further up the file silently
   loses. */

@media (max-width: 575.98px) {
    /* The masthead is the brand and the way out, nothing else. The signed-in address wraps the bar
       onto two lines and squeezes the wordmark; at this width it belongs on the page, not in the
       chrome. */
    .br-masthead__who {
        display: none;
    }

    /* "TrainedResponder" drops and the mark carries it. Nothing is lost that was not already being
       carried — the mark IS the TrainedResponder mark, and the footer line is still on the page.
       The alternative is shrinking "BlockReady", which is the one word a neighbor standing in the
       street needs to recognise. Its trailing gap goes with it, so nothing is left hanging. */
    .br-masthead__house {
        display: none;
    }

    .br-masthead__mark {
        height: 28px;
    }

    /* A row that is a link stacks rather than squeezing its description into a sliver. */
    .br-list a {
        flex-direction: column;
        align-items: flex-start;
        gap: var(--br-space-1);
    }

    .br-page-head h1 {
        font-size: var(--br-text-h1);
    }

    /* Bootstrap's container gives 12px of gutter, which puts a sentence almost against the glass —
       and against the curved edge and gesture area of most phones. A thumb rests there. */
    .container {
        padding-left: var(--br-space-5);
        padding-right: var(--br-space-5);
    }

    /* A 7rem label column and a row of chips do not both fit at 390. The label becomes a line of
       its own above its answers, which is what it already looks like it wants to be. */
    .br-roster__facts {
        grid-template-columns: 1fr;
        gap: var(--br-space-1);
    }

    .br-roster__facts dt {
        padding-top: var(--br-space-2);
    }

    .br-roster__row {
        padding: var(--br-space-4);
    }

    /* Two cards side by side at 390 is two cards nobody can read. */
    .br-doors {
        grid-template-columns: minmax(0, 1fr);
    }

    /* The trail keeps the last two steps and drops the rest. A full trail wraps to two lines at 390,
       and the wrap puts a separator at the start of the second line, which reads as a bullet. What a
       phone needs is the way back one level, and that is the second-to-last step. */
    .br-trail__steps li:not(:nth-last-child(-n + 2)) {
        display: none;
    }

    /* The step that is now first must not keep the separator it had when something preceded it. */
    .br-trail__steps li:nth-last-child(2)::before {
        content: none;
    }
}
