/* The public Stoop board — cards, not rows.
 *
 * The signed-in console draws this board as `.rhrow`: one text line per item with the photo
 * squeezed in beside it. That is the right shape for somebody managing their own listings and
 * the wrong shape for a stranger deciding whether a chair is worth a walk. A stranger reads the
 * PHOTO first, then what it is, then how far. So: cards.
 *
 * Mobile-first at 390. One column until there is room for two, three at desk width. Every colour,
 * space and radius below is a token from design-system/tokens.json via
 * frontend/css/design-tokens.generated.css — no literal hex, no invented spacing step.
 */

.stoop-hero {
  padding: var(--space-6) var(--space-4) var(--space-5);
  max-width: 68rem;
  margin: 0 auto;
}
.stoop-hero h1 { margin: 0 0 var(--space-3); }
.stoop-lede {
  margin: 0 0 var(--space-3);
  color: var(--ink-2);
  /* A reading measure, not a page width. 44ch was tight enough that the sentence broke into
     four short lines on a laptop while the filter row beside it ran the full 68rem; 56ch is
     still inside the comfortable range and stops the lede looking like a column of fragments. */
  max-width: 56ch;
}

/* THE HERO ON A WIDE SCREEN — measured on the live page at 1280 before this rule existed:
 *
 *   .stoop-hero      1088 px      the page column
 *   h1                405 px
 *   .stoop-lede       380 px      44ch
 *   .stoop-truth      449 px      52ch
 *
 * Every child sat at a reading measure inside a 1088 px box, so 59% of the hero was empty and
 * the page's first screen read as a phone layout stretched onto a laptop — the owner's words
 * were "still old school old fashion". The filter row directly below it runs the full 1088, so
 * the eye also crossed two different content widths within one screen.
 *
 * The fix is NOT to widen the text: a 1088 px line of body copy is unreadable, and the 56ch
 * measure above is the correct one. It is to give the second column something true to hold.
 * The trust statement is that thing — "anyone can look, claiming needs an account, and here is
 * why" is exactly what a stranger is deciding when they first land, so it belongs beside the
 * headline rather than stacked under it in the same narrow track.
 *
 * Below 62rem nothing changes: the grid collapses to the single column the phone already had,
 * which is the layout the 390 screenshot showed working.
 */
@media (min-width: 62rem) {
  .stoop-hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
    column-gap: var(--space-6);
    align-items: start;
  }
  .stoop-hero h1,
  .stoop-lede { grid-column: 1; }
  /* Spans both rows on the right so the card's top aligns with the headline's cap height
     rather than floating against the lede's second line. */
  .stoop-truth {
    grid-column: 2;
    grid-row: 1 / span 3;
    max-width: none;
    align-self: start;
  }
  .stoop-hero__actions { grid-column: 1; margin-top: var(--space-4); }
}
/* The signed-out truth, at the top, before anybody taps anything. */
.stoop-truth {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--card-2);
  color: var(--ink-2);
  max-width: 52ch;
}
.stoop-truth b { color: var(--ink); }

/* The supply door. Wraps rather than shrinks: at 390 the aside drops under the button instead of
   squeezing it below a thumb's worth of width. */
.stoop-hero__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin: var(--space-4) 0 0;
}
.stoop-hero__aside {
  color: var(--ink-3);
  font-size: .875rem;
  line-height: 1.45;
  max-width: 34ch;
}
.stoop-empty .z-btn { margin-top: var(--space-3); }

/* THE WATCH DOOR. Two of them: one with the filters, one in the empty state -- because the
   moment somebody most wants to be told about the next chair is the moment they have just found
   there are none. The empty board used to end at "check back tomorrow" plus a LISTING button,
   which serves the visitor who came to give and abandons the one who came to take.

   Deliberately not the primary green: on this page the loudest thing stays "List something
   free", because supply is the scarce half. This is the quiet second door. */
.stoop-filter--watch { justify-content: flex-end; }
.stoop-watch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  text-align: center;
  /* The label carries a neighborhood name, so it is the one control here whose width is not
     knowable in advance. It wraps rather than clipping -- a bell that reads "Tell me when
     something lands in Staten Isla..." is worse than two lines. */
  white-space: normal;
  line-height: 1.25;
}
.stoop-empty .stoop-watch { margin-top: var(--space-2); }
/* A sibling branch (fix/the-board-stops-looking-like-a-form) turns .stoop-filters into a
   two-column grid at phone width. This label carries a neighborhood name, so it is one of the
   two controls whose width is not knowable in advance -- it takes the whole row there for the
   same reason the search box does. Written now rather than later because the two changes land
   days apart and the composition is the part nobody looks at. */
.stoop-filters .stoop-filter--watch { grid-column: 1 / -1; }
@media (min-width: 720px) { .stoop-filter--watch { flex: 1 1 18rem; } }

/* ---- filters -------------------------------------------------------------- */
/*
 * WHY THIS STOPPED BEING A COLUMN OF FORM FIELDS.
 *
 * Owner, on this page: "still old school old fashion ... you might think about it as modern
 * cards - filters - status". Looked at on a phone, the complaint was exact. Six controls
 * stacked vertically, each under its own small grey caption, ran the board off the bottom of
 * the screen: a visitor scrolled past a search FORM to reach the thing they came for. That
 * is the shape of a 2010 advanced-search panel, not of somewhere you browse.
 *
 * Two changes, both CSS only. This page's stylesheet is loaded on its own rather than
 * through the bundle, and the ?v= is stamped at image build, so none of this re-renders a
 * page or moves a bundle hash.
 *
 * 1. A GRID, not a stack. Two columns at 390 halves the height, and the search box spans
 *    both because a search term is longer than a dropdown value.
 * 2. ONE BASELINE. `align-items: flex-end` lined the controls up by their bottoms, so any
 *    control of a different height dragged its own caption out of line -- which is exactly
 *    what "Sort by what's near me" did, sitting several pixels above the other four at
 *    desk width. Every control is the same height now and the row is aligned by `stretch`,
 *    so the fault cannot come back by adding a sixth control.
 */

/*
 * AND WHY IT STILL DID NOT WORK ON A PHONE.
 *
 * The grid above halved this block and at desk width it worked: 158px, first listing at
 * y=636 in a 900px viewport. At 390 it did not, and the seventh cell (the Alerts bell)
 * made it worse. Measured on main, served from disk with production's real payload,
 * Chromium 390x844:
 *
 *     y515: Neighborhood 173w                      <- alone, right half empty
 *     y600: Distance      358w
 *     y685: Within        173w                      <- alone, right half empty
 *     y770: Search        358w
 *     y855: Order 173w | Still on the clock 173w
 *     y940: Alerts        358w
 *
 * Six rows for seven cells, 499px of controls, and the only listing on the board at
 * y=1063 -- 219px BELOW the fold. A visitor on a phone scrolls past a form to reach the
 * thing they came for.
 *
 * 1. `grid-auto-flow: dense` backfills those two empty half-cells. Auto placement cannot
 *    pull `Within` up beside `Neighborhood` by itself: the full-width `Distance` sits
 *    between them in source order, and a sparse grid never goes back to fill a hole.
 *
 * 2. The block FOLDS below 720px, behind one 44px bar (stoop.js builds it, so a browser
 *    with no script keeps every control exactly as it is today). Nothing is removed --
 *    every filter is one tap away, full width, at its own height -- and the bar counts how
 *    many are set, because a folded filter must never be a silent one. Above 720px the
 *    fold is inert: the bar is not drawn and the grid already solved this.
 *
 * The listing lands at y=600 rather than y=1063: the board shows a thing before a form.
 */

.stoop-fold { display: block; }
.stoop-fold__bar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 44px; /* the same thumb floor every control in this block uses */
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4);
  cursor: pointer;
  font-weight: 600;
  color: var(--ink);
  list-style: none; /* Firefox draws a triangle from the list marker */
}
.stoop-fold__bar::-webkit-details-marker { display: none; }
.stoop-fold__bar::after {
  content: '';
  margin-left: auto;
  width: .5rem;
  height: .5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform .15s ease;
}
.stoop-fold[open] > .stoop-fold__bar::after { transform: rotate(-135deg); }
.stoop-fold__count { color: var(--ink-3); font-weight: 400; }
@media (min-width: 720px) {
  /* stoop.js forces the details open at this width; the bar would be noise beside a row
     that already fits. */
  .stoop-fold__bar { display: none; }
}

.stoop-filters {
  /* `dense` backfills the half-cells the full-width children leave behind: three of the seven span
     both columns and sit BETWEEN the pairable ones in source order, so sparse auto-placement left
     `Neighborhood` and `Within` alone on their rows with an empty 173px beside each. Declared HERE,
     inside the one rule for this selector, rather than in a second rule for the same selector — a
     duplicate selector is how `the filters are laid out, not stacked` started reading the wrong
     block and failed on a change that did not touch the layout it guards. NO BRACES IN THIS
     COMMENT: that guard finds a rule by scanning for its selector and then to the next closing
     brace, so a brace written here ends the block early and the rule below reads as empty. */
  grid-auto-flow: dense;
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-4);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3);
  align-items: stretch;
}
/* The search term is the one control whose content is not a short fixed phrase. */
.stoop-filters .stoop-filter:has(input[type="search"]) { grid-column: 1 / -1; }
/* "Sort by what's near me" is a sentence, not a word, and it is the one control a stranger is
   most likely to want. Half of 390px clipped it to "Sort by what's near m..."; it gets the whole
   row, which also reads as the emphasis it deserves.

   BOTH IDS, because this is one bar on two surfaces now. The public page's button is #stoopNear
   and the console's is #rhNear; keying the rule to one of them is how the two boards start
   looking different again, which is the drift this file exists to end. The search box needs no
   such pairing — `input[type="search"]` already matches #stoopSearch and #rhQ alike. */
.stoop-filters .stoop-filter:has(#stoopNear),
.stoop-filters .stoop-filter:has(#rhNear) { grid-column: 1 / -1; }
@media (min-width: 720px) {
  .stoop-filters { display: flex; flex-wrap: wrap; align-items: stretch; }
  .stoop-filters .stoop-filter:has(input[type="search"]) { flex: 1 1 14rem; }
}
.stoop-filter { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; justify-content: flex-end; }
.stoop-filter > span { font-size: .75rem; color: var(--ink-3); letter-spacing: .02em; }
/* The button is included on purpose. It was a .z-btn of its own height, which is what threw
   the row out of line; a filter control should look and measure like the filter controls
   beside it, whatever element it happens to be. */
.stoop-filter select,
.stoop-filter input[type="search"],
.stoop-filter > #stoopNear,
.stoop-filter > #rhNear {
  min-height: 44px; /* the thumb floor the primitives use everywhere else */
  height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--bg-2);
  color: var(--ink);
  font: inherit;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stoop-filter input[type="search"] { min-width: 12rem; }
.stoop-filter select:disabled { opacity: .55; }
.stoop-check { flex-direction: row; align-items: center; gap: var(--space-2); min-height: 44px; }
.stoop-check input { width: 20px; height: 20px; accent-color: var(--brand); }
.stoop-check span { font-size: .9375rem; color: var(--ink-2); }
#stoopNear[aria-pressed="true"],
#rhNear[aria-pressed="true"] { border-color: var(--brand); color: var(--brand-ink); }
.stoop-nearnote,
.stoop-status {
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-3);
  color: var(--ink-3);
  font-size: .875rem;
}
.stoop-nearnote:empty, .stoop-status:empty { padding: 0; }
.stoop-count {
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-2);
  color: var(--ink-3);
  font-size: .875rem;
}

/* ---- the grid ------------------------------------------------------------- */

.stoop-grid {
  list-style: none;
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-6);
  display: grid;
  grid-template-columns: 1fr;          /* 390: one card, full bleed inside the gutter */
  gap: var(--space-4);
}
@media (min-width: 640px) { .stoop-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .stoop-grid { grid-template-columns: repeat(3, 1fr); } }

.stoop-card {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  background: var(--card);
  overflow: hidden;
}
.stoop-card__photo {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  background: var(--card-2);
}
/* A LISTING WITHOUT A PHOTO IS STILL A LISTING, NOT A HOLE.
 *
 * `--none` inherited the 4/3 box its sibling needs for a real photograph, so an item with
 * no picture spent roughly 290px of a 390px screen on a grey rectangle reading "No photo"
 * -- on a board whose whole job is to make somebody want to walk over and take a chair.
 * Measured on production: four of the five listings have no photo of their own, so this is
 * the common card, not the exception.
 *
 * A slim band instead. It keeps the cards on a rhythm in the 2- and 3-column grids, which
 * dropping the band entirely would not, and it lets the label be the first thing read. */
.stoop-card__photo--none {
  aspect-ratio: auto;
  min-height: var(--space-7);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-3);
  font-size: .8125rem;
  border-bottom: 1px dashed var(--line);
}
/* THE MARK THAT REPLACED THE WORDS "No photo". Deliberately faint: it is texture telling the eye
   there is no picture here, not an icon asking to be tapped. The band's own size is set above and
   is not touched — that rule is somebody else's and it is already correct. */
.stoop-card__mark {
  /* 24px inside the band's 48px min-height: half the band, so it centres with real air around it.
     32px filled two thirds of it and started reading as content. */
  width: var(--space-5);
  height: var(--space-5);
  opacity: .3;
}
.stoop-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  flex: 1;
}
.stoop-card__label { margin: 0; font-size: 1.0625rem; line-height: 1.3; color: var(--ink); }
.stoop-card__meta { margin: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); font-size: .875rem; }
.stoop-card__where { color: var(--ink-2); }
/* WHAT KIND OF THING, first in the meta line and quieter than the distance.
 *
 * It sits beside the neighbourhood rather than above the label because it is the fastest thing
 * an eye REJECTS on — scanning for a desk, a reader discards a fridge on the word. Deliberately
 * not the state chip's treatment (uppercase, bordered, coloured by status): a category is a fact
 * about the object, and the status chip is a fact about whose move it is. Two different kinds of
 * fact should not be able to look like each other on the same card. */
.stoop-card__kind {
  color: var(--ink-2);
  background: var(--card-2);
  border-radius: var(--radius-control);
  padding: 2px var(--space-2);
}
.stoop-card__far {
  color: var(--brand-ink);
  background: var(--brand-tint);
  border-radius: var(--radius-control);
  padding: 2px var(--space-2);
}
.stoop-card__clock { margin: 0; font-size: .8125rem; color: var(--ink-3); }
/* A fact about the PERSON, not about the item -- so it gets its own line rather than
   joining the neighborhood/distance run or the clock. The signed-in console learned that
   from a screenshot at 390, where a glued-on version read as one run-on sentence. */
.stoop-card__trust { margin: 0; font-size: .8125rem; color: var(--ink-2); font-weight: 600; }
.stoop-card__claim { margin-top: auto; align-self: flex-start; }

/* ---- empty, and the one paid door ---------------------------------------- */

.stoop-empty {
  max-width: 44rem;
  margin: 0 auto var(--space-6);
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--ink-2);
}
.stoop-empty h2 { margin: 0 0 var(--space-2); }

/* ONE door into the paid product, and it sits BELOW the free board in its own band so it can
 * never be mistaken for part of a listing. Quiet on purpose: a stranger looking at free
 * furniture is the cheapest lead there is, and the way to lose them is to shout. */
.stoop-paid {
  border-top: 1px solid var(--line);
  background: var(--bg-2);
}
.stoop-paid__inner {
  max-width: 68rem;
  margin: 0 auto;
  padding: var(--space-6) var(--space-4);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  justify-content: space-between;
}
.stoop-paid h2 { margin: 0 0 var(--space-1); font-size: 1.125rem; }
.stoop-paid p { margin: 0; color: var(--ink-2); max-width: 48ch; }

/* HOW A HANDOFF WORKS — the sequence, for somebody who has not signed in.
 *
 * A numbered list because this IS a sequence: step 4 cannot happen before step 2, and the order
 * carries the information. The number comes from a counter rather than the list marker so it can
 * hold its own column and a step that wraps to two lines keeps a straight text edge.
 *
 * Two columns from 720px, the same breakpoint the filter block folds at. Five steps in one column
 * on a wide screen is a thin ribbon down the left with the page empty beside it. */
.stoop-how {
  max-width: 68rem;
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) 0;
}
.stoop-how h2 { margin: 0 0 var(--space-3); font-size: 1.125rem; }
.stoop-how__steps {
  list-style: none;
  counter-reset: stoop-step;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2) var(--space-5);
}
@media (min-width: 720px) {
  .stoop-how__steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* NOT A GRID, AND THAT IS THE WHOLE NOTE.
 *
 * First version made the `li` a two-column grid — number, then text. Each step has THREE grid
 * children, not two: `::before`, the `<b>`, and the bare text node after it, which becomes an
 * ANONYMOUS grid item and lands in the next row's 1.75rem number column. The result rendered one
 * character per line down a 4px ribbon.
 *
 * Every measurement was green: five steps, two columns, nothing off-screen, no page overflow. The
 * screenshot is the only instrument that saw it. A grid whose children you did not count is a
 * layout you have not read.
 *
 * So the number is taken out of flow instead, and the step stays one ordinary block of text that
 * wraps the way prose wraps, however many inline elements it contains. */
.stoop-how__steps li {
  counter-increment: stoop-step;
  position: relative;
  padding: var(--space-1) 0 var(--space-1) 2rem;
  color: var(--ink-2);
  font-size: .9375rem;
  line-height: 1.5;
}
.stoop-how__steps li::before {
  content: counter(stoop-step);
  position: absolute;
  left: 0;
  top: var(--space-1);
  width: 1.25rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--brand);
}
.stoop-how__steps b { color: var(--ink); font-weight: 600; }

.stoop-safety {
  max-width: 68rem;
  margin: 0 auto;
  padding: var(--space-5) var(--space-4) var(--space-7);
  color: var(--ink-3);
  font-size: .875rem;
}
.stoop-safety ul { margin: var(--space-2) 0 0; padding-left: 1.1rem; }
.stoop-safety li { margin: 0 0 var(--space-1); }

/* ===========================================================================
 * THE SAME CARD, SIGNED IN.
 *
 * Owner, 2026-09-23, about the console: "still old school old fashion … modern cards - filters -
 * status". The public board shipped as cards the day before and the signed-in one — the only
 * Stoop he ever sees — kept drawing `.rhrow` flat lines. Everything above this comment is now
 * shared: frontend/js/dashboard.js draws `.stoop-card` through the same module the public page
 * uses, so there is ONE definition of a Stoop card and a change to the shape lands on both
 * surfaces or on neither.
 *
 * What is below is the part the public page has no use for, because a stranger has no handoff:
 * the STATUS, made the structure of the card rather than a sentence trailing after the label.
 * =========================================================================== */

/* Inside the console the board already sits in a `.card > .card-body`, which owns the gutter and
 * the width. These two only ever undo the page-level frame; the card, the grid gaps and every
 * colour stay exactly as the public board has them. */
.stoop-grid--console,
.stoop-filters--console { max-width: none; margin: 0; padding: 0; }
/* ROOM FOR THE SUPPORT LAUNCHER, WHICH IS FIXED OVER THE END OF THIS BOARD.
 * .zs-launcher is position:fixed, bottom:max(18px, safe-area), z-index 89, min-height 48px, in
 * the bottom-right corner. The console board ended 8px below its last card, so on a walk of the
 * signed-in board at 390 an elementsFromPoint probe over the last row returned the LAUNCHER, not
 * the card control underneath it — Block, on the second listing.
 *
 * A control that is in the DOM, visible, enabled and covered by a fixed element is unreachable,
 * and NOTHING goes red: every test that asks whether the button exists passes. The same class as
 * a rail item rendered off-screen. Measured rather than reasoned about, by another lane.
 *
 * Height + inset + a gap, so the last card clears it on any viewport and the safe-area inset is
 * respected on a notched phone. A board is the one place this matters, because it is the only
 * screen whose last row is both scrollable and full of actions. */
.stoop-grid--console { padding-bottom: calc(max(18px, env(safe-area-inset-bottom)) + 62px); }
.stoop-filters--console { padding-bottom: var(--space-4); }
/* The console's column is narrower than a full page, so the grid reads its own width rather than
 * the viewport's. At 390 that is one card, exactly as the public board. */
.stoop-grid--console { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
@media (min-width: 640px) { .stoop-grid--console { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); } }
@media (min-width: 1000px) { .stoop-grid--console { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); } }

/* WHOSE MOVE IS IT — first thing in the card, above the label.
 *
 * It used to be a `.pill` beside the item's name with the sentence trailing underneath, which is
 * the shape an eye skips: the reader is looking for the thing, finds it, and stops. Putting the
 * state above the thing is the whole difference between a board that lists items and a board two
 * people can continue something on. */
.stoop-card__chip {
  margin: 0;
  align-self: flex-start;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--card-2);
  color: var(--ink-2);
  border: 1px solid var(--line);
}
/* Four states, four colours, and a border on every one of them so the state survives a reader who
 * cannot separate the hues. Tokens only — no literal hex anywhere on this board. */
.stoop-card--wait .stoop-card__chip { background: var(--gold-tint); color: var(--gold); border-color: var(--gold); }
.stoop-card--go   .stoop-card__chip { background: var(--brand-tint); color: var(--brand-ink); border-color: var(--brand); }
.stoop-card--over .stoop-card__chip { background: var(--coral-tint); color: var(--coral); border-color: var(--coral); }
.stoop-card--done .stoop-card__chip { background: var(--info-tint); color: var(--info); border-color: var(--info); }
.stoop-card--mine .stoop-card__chip { background: var(--card-2); color: var(--ink-2); }
/* The state is also the card's left edge, so a board of ten handoffs can be read down the side
 * without reading a word of it. */
.stoop-card--wait { border-left: 3px solid var(--gold); }
.stoop-card--go   { border-left: 3px solid var(--brand); }
.stoop-card--over { border-left: 3px solid var(--coral); }
.stoop-card--done { border-left: 3px solid var(--info); }

/* WHICH OF THE TWO PEOPLE YOU ARE. "You put this out · Neighbor 82b91e is collecting it".
 * The old row was symmetrical — it read identically to the giver and the collector — so each of
 * them had to remember their own side of something they arranged days ago. */
.stoop-card__relation { margin: 0; font-size: .875rem; color: var(--ink); font-weight: 600; }
/* How many handoffs this stranger has completed, and nothing else about them. */
.stoop-card__trust { margin: 0; font-size: .8125rem; color: var(--ink-3); }
/* The next move, in one sentence, for THIS side. */
.stoop-card__next { margin: 0; font-size: .875rem; line-height: 1.45; color: var(--ink-2); text-wrap: pretty; }
/* WHERE, as the giver typed it (migration 123). A sentence — "front stoop, buzzer 2B" — never an
 * address and never a coordinate, because neither of those may cross this wire. It is set in ink,
 * not ink-3: at the moment a collector opens this card it is the single thing they came to read,
 * so it must not be quieter than the status line above it. `overflow-wrap` because a buzzer code
 * or a cross-street can be one long unbroken token and a 390px card has nowhere to put it. */
.stoop-card__place { margin: 0; font-size: .875rem; line-height: 1.45; color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }

/* The buttons. They wrap rather than shrink: at 390 three controls squeezed onto one line is
 * three targets under a thumb's width, which is how the wrong one gets pressed. */
.stoop-card__actions {
  margin-top: auto;
  padding-top: var(--space-1);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
/* A BUTTON WRAPS TO THE NEXT LINE; IT DOES NOT WRAP ITS OWN LABEL.
 *
 * Found by looking at the 390 shot, which is the only way it could be found. `flex-wrap: wrap`
 * alone still lets a flex item SHRINK below its content before it agrees to move down a line, so
 * a collector's card with three controls squeezed "Message Neighbor b375bc" into a 90px column
 * reading one word per line, six lines tall, beside two buttons with room to spare. The geometry
 * was legal — nothing overflowed and nothing clipped — and it looked broken.
 *
 * flex-shrink: 0 means each control keeps the width its label needs and the row wraps instead.
 * max-width caps the one case that could then overflow, a pseudonym longer than the card. */
.stoop-card__actions > * { flex: 0 0 auto; max-width: 100%; }
.stoop-card__actions .linkbtn,
.stoop-card__actions .dv2-act { min-height: 40px; }
.stoop-card__said { color: var(--ink-3); font-size: .8125rem; }

/* A PERSON IS NOT A LISTING. The one list on the Stoop that stays a list: whom you blocked, and
 * the way back. A card would be a photo tile of nothing. */
.stoop-person {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink-2);
  font-size: .9375rem;
}
.stoop-person:last-child { border-bottom: 0; }

/* The console's empty board sits inside the card body, so it drops the page-level frame. */
.card-body > .stoop-empty { margin: 0; padding: var(--space-5) var(--space-3); }
.card-body > .stoop-empty h2 { font-size: 1.125rem; }

/* THE LINK YOU CAN SEND, AND THE ANSWER TO FOLLOWING ONE.
   IN THIS FILE, NOT styles.css. /stoop loads exactly two stylesheets - lv-assets and this one -
   and never css/styles.css. The first version of these rules went into styles.css and a test
   "verified" them by reading that file, which was true and irrelevant: the page they are for does
   not load it, so on screen there was no highlight at all. Read at 390 by eye; no count caught it,
   because the class WAS on the element. */

/* The share control sits in the card's own action row, so `.stoop-card__actions .linkbtn` above
   already gives it the right size and hit area. Quieter than "Sign in to claim", because sharing
   is the second thing you do with a free sofa and never the first. */
.stoop-card__actions .stoop-card__share { font-weight: 600; opacity: .85; }
.stoop-card__actions .stoop-card__share:hover,
.stoop-card__actions .stoop-card__share:focus-visible { opacity: 1; }

/* THE ONE SOMEBODY SENT YOU. An outline rather than a tinted background: the card sets its own
   background and border-radius, so a background tint is simply overwritten and the reader is told
   "here it is" above a card that looks like all the others. */
.stoop-card.rh-wanted { outline: 2px solid var(--brand, currentColor); outline-offset: 2px; }

/* THE SENTENCE A SHARED LINK IS ANSWERED WITH, above the board.
   It is spoken TO a person who just followed a link, so it must not inherit `.fineprint` - mono,
   0.72rem, centred, ink-3, which is the voice this site uses for legal small print. At 1280 it
   rendered as a grey centred whisper wedged between "3 things on the board" and the grid, which
   is not an answer to "where is the thing I was sent". Read by eye; every count said it was there.
   Never an error colour either: "somebody already took it" is a board doing its job. */
.stoop-arrival {
  /* The SAME container idiom as .stoop-count and .stoop-grid, which are its siblings. This page
     insets each child itself rather than wrapping them, so a `max-width` of my own put the
     sentence hard against the left edge of the viewport while every other line on the page began
     at the gutter. Seen at 1280; at 390 the gutter is small enough that it looked fine. */
  max-width: 68rem;
  margin: 0 auto;
  padding: 0 var(--space-4) var(--space-2);
  font-family: inherit;
  font-size: .98rem;
  line-height: 1.5;
  text-align: left;
  color: var(--ink-2, inherit);
}

/* ===========================================================================
 * THE MARKETPLACE HUB (migration 139).
 *
 * Owner, 2026-09-30: "modern cards, filters, status … the relationship between the poster and the
 * collector is not clear enough." The poster now CHOOSES who collects, so the board grows three
 * things a card never needed before: a row of chips (status + "Zeft crew"), a list of people who
 * asked, and a stepper that says how far one giveaway has come. And the Stoop gets three tabs —
 * Board · My posts · My requests — so each person reads their own side of every handoff in one
 * place. Tokens only; no literal colour.
 * =========================================================================== */
.stoop-card__chips { margin: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); }
.stoop-card__chips .stoop-card__chip { display: inline-block; }
.stoop-card__chip--asked-by-you,
.stoop-card__chip--asked-count { background: var(--brand-tint); color: var(--brand-ink); border-color: var(--brand); }
.stoop-card__chip--ending { background: var(--coral-tint); color: var(--coral); border-color: var(--coral); }
.stoop-card__chip--new { background: var(--gold-tint); color: var(--gold); border-color: var(--gold); }
.stoop-card__badge {
  font-size: .75rem;
  font-weight: 600;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--info-tint);
  color: var(--info);
  border: 1px solid var(--info);
}
/* Who can take it: "Neighbors first for 5h" / "Open to neighbors and crews". */
.stoop-card__audience { margin: 0; font-size: .8125rem; color: var(--ink-3); }
/* The collector's spot line before both agreed: a promise about WHEN, never a location. */
.stoop-card__held { margin: 0; font-size: .875rem; color: var(--ink-2); font-style: italic; }
.stoop-card__maps { font-weight: 600; margin-left: var(--space-1); white-space: nowrap; }
.stoop-card__fine { margin: 0; font-size: .8125rem; color: var(--ink-3); line-height: 1.4; }
.stoop-card__actions > .stoop-card__fine { flex: 1 1 100%; }

/* THE STEPPER. A row of short step names; the ones behind you are filled, the current one is
 * bold, the rest are quiet. It wraps at 390 rather than scrolling sideways. */
.stoop-steps {
  list-style: none;
  margin: var(--space-1) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  font-size: .75rem;
}
.stoop-steps__step {
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  border: 1px solid var(--line);
  color: var(--ink-3);
  background: var(--card);
}
.stoop-steps__step.is-done { color: var(--brand-ink); border-color: var(--brand); background: var(--brand-tint); }
.stoop-steps__step.is-current { color: var(--ink); border-color: var(--ink-2); font-weight: 700; }

/* THE TABS. Three targets, 44px tall, one row and one line each even at 390. Equal thirds put
   "My requests · 1" on two lines at 390 (QA 2026-09-30): the thirds stay equal while they fit,
   and a tab never gets narrower than its own words. */
.stoop-tabs {
  display: flex;
  gap: var(--space-1);
  margin: var(--space-2) 0 var(--space-4);
  padding: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--card-2);
  border: 1px solid var(--line);
}
.stoop-tab {
  min-height: 44px;
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-weight: 600;
  font-size: .875rem;
  cursor: pointer;
  padding: 0 var(--space-2);
  flex: 1 1 0;
  min-width: max-content;
  white-space: nowrap;
}
/* Below 360px a label may wrap rather than overflow the pill. */
@media (max-width: 359px) { .stoop-tab { min-width: 0; white-space: normal; overflow-wrap: anywhere; } }
.stoop-tab.is-on { background: var(--card); color: var(--ink); box-shadow: 0 1px 2px var(--line); }
.stoop-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* THE PEOPLE WHO ASKED, on the poster's own card. A person is not a listing, so this is a list. */
.stoop-asks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.stoop-ask {
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: var(--space-2) var(--space-3);
  background: var(--card-2);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.stoop-ask__who { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); font-size: .875rem; color: var(--ink); }
.stoop-ask__who small { color: var(--ink-3); font-size: .8125rem; }
.stoop-ask__note { margin: 0; font-size: .875rem; color: var(--ink-2); overflow-wrap: anywhere; }
.stoop-ask__acts { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.stoop-ask__acts > * { flex: 0 0 auto; min-height: 40px; }

/* The console fold sits inside the card body; it drops the page-level centring. */
.stoop-fold--console .stoop-fold__bar { max-width: none; margin: 0; }
/* The booking screen's one line about a Stoop discount the server will apply. */
.stoop-booking-discount { color: var(--brand-ink); font-weight: 600; }

/* ---- Stoop engagement (spec 2026-09-30): From, saved searches, On your way ------------------- */
/* The saved-search door sits beside the bell and, like it, carries text whose width is not
   knowable in advance, so it takes the whole row on a phone and wraps rather than clipping. */
.stoop-filters .stoop-filter--save { grid-column: 1 / -1; }
@media (min-width: 720px) { .stoop-filter--save { flex: 1 1 18rem; } }
.stoop-save { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; white-space: normal; line-height: 1.25; text-align: center; }
/* "You pick it up" under an Anywhere card, and the stop line on an On-your-way card. */
.stoop-card__pickup, .stoop-card__onroute { margin: var(--space-1, .25rem) 0 0; font-size: .875rem; color: var(--ink-2, #475569); }
.stoop-card__onroute { font-weight: 600; }
.stoop-empty__within { display: flex; flex-wrap: wrap; gap: var(--space-2, .5rem); justify-content: center; }
.rhonroute { margin: 0 0 var(--space-4, 1rem); }
.rhsaved__row { flex-wrap: wrap; gap: var(--space-2, .5rem); }
.rhsaved__tools { display: inline-flex; flex-wrap: wrap; gap: var(--space-2, .5rem); align-items: center; }
/* The saved row's distance picker reads like the filter selects above it. Unstyled it fell back to
   the browser's dark native control (seen at 390 and 1280, 2026-10-02). */
.rhsaved__tools select {
  min-height: 36px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
  background: var(--bg-2);
  color: var(--ink);
  font: inherit;
}
.rhsaved__save { margin: var(--space-2, .5rem) 0; }
