
/* ------------------------------- the home page's own widths

   Five rules, every one qualified with body.home. The two routes that pass
   `home` (routes/public.js) pass `area: 'landing'` with it, so this file is
   loaded on exactly the pages they can match. They are first in the file so
   that every rule after them wins its ties with them, the 1240px band at
   1320px included. */

/* The home page is the one page whose content is a 940px table sitting inside
   a 1100px shell, so its wordmark started 60px inside the table's own left
   edge. 940 of table plus this header's own 20px of padding either side is
   980: the logo then starts where the table starts and the last nav link ends
   where it ends. Two classes deep on purpose, so it beats the 1240px rule in
   the three-per-row band without having to be written out again there, and so
   /sponsors (body.home, not .wide, a grid rather than a table) is left alone.
   The footer carries the same width for the same reason. */
body.home.wide header.top { max-width: 980px; }

/* The home page is one column, not a table inside a wider shell. Everything
   above and below the directory, the hero, the booking line and 'How it
   works', now starts and ends where the table does. box-sizing is border-box
   everywhere on this site, so main's own 20px of padding either side comes
   out of the number: 980 minus 40 is the 940 the table asks for, and the two
   left edges land on the same pixel. Two classes deep on purpose, so it beats
   the 1100px body.wide rule in base.css and the 1240px band at 1320px without
   being repeated there, and so /sponsors (body.home, not .wide) keeps its
   grid. */
body.home.wide main { max-width: 980px; }

/* Squared off with the table above it, same arithmetic as the home header. */
body.home.wide footer { max-width: 980px; }

/* The two 980s above put the content on the table's edges, but a box of 980
   draws its hairline 40px wider than the 940 column: the widest thing on the
   page would be a rule nobody is meant to notice. From the width where the
   column really is 940, the box is 940 too and gives up its side padding, so
   the line stops where the table stops and the wordmark still starts on the
   table's left edge. Below 980 the padding is back and nothing moves. */
@media (min-width: 980px) {
  body.home.wide header.top,
  body.home.wide footer {
    max-width: 940px;
    padding-left: 0;
    padding-right: 0;
  }
}

/* The home page centres the list under its heading. */
body.home .steps { margin: 0 auto 16px; }


/* ------------------------------------------------------------------ hero */

/* Nothing stands over the headline, so the hero opens on it and 24px of top
   padding is enough: the first thing in the box is the heading itself, and
   that is the whole of the room the faces row costs. The hero is written here
   because this page is the only one that draws one. */
.hero {
  text-align: center;
  padding: 24px 0 8px;
}

/* One rule for every width instead of a desktop size and a phone size. The
   ceiling is the largest size at which the headline still holds one line
   inside the 940px column: at 40px the sentence ran about 1010px, and 36px
   brings it to roughly 909px with a little room to spare. The floor is the
   phone size and the 3.9vw between them takes a tablet down smoothly rather
   than in a jump. */
.hero h1 { font-size: clamp(26px, 3.9vw, 36px); }

.hero .sub { margin-bottom: 32px; }

.hero .rule { margin-top: 14px; }

/* The two lines under the headline: what a sponsor gets, then how the money
   is handled. Wide enough that each sentence holds its own line on a laptop,
   tight enough that the pair reads as one block rather than two paragraphs.
   On a phone they wrap, which is what the break is there for.

   Prompt M1 owns the two vertical numbers. The sub and the button are two
   steps of one block: 24px of air under the sentence on a phone, 32px from
   700px up (the block at the end of this section), and the button closes the
   hero, so it needs no space held for another door underneath it.

   THE SIDE MARGINS STAY auto. The 64ch ceiling makes this box narrower than
   the hero, and it is the auto that puts it on the same axis as the headline
   and the button; a 0 there drops the whole line to the left edge of the
   column while its own text stays centred inside it. Set the two vertical
   numbers, never the sides. */
.hero .herosub {
  max-width: 64ch;
  margin: 0 auto 24px;
  color: var(--muted);
  line-height: 1.45;
}

.hero .herosub + p { margin: 0; }

@media (max-width: 700px) {
  /* On a phone the hero competes with the table for the first screen, so
     every gap inside it gives a few pixels back to the first row. Desktop
     keeps the roomier set above. */
  .hero { padding-top: 24px; padding-bottom: 4px; }
  .hero .herotrust + p { margin-bottom: 8px; }
}

@media (min-width: 700px) {
  .hero .herosub { margin-bottom: 32px; }
}

/* ----------------------------------------------------- marketplace cards */

/* The grid and the card. The home page and /sponsors are the only two pages
   that draw one and both load this file. */

.mgrid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

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

/* A sponsor card is a panel that is a link, so what is written here is how
   the card is laid out inside the box. */
.mcard {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.mcard-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.mcard-stats {
  display: flex;
  flex-wrap: wrap;
  border-top: 1px solid var(--hairline);
  padding-top: 12px;
  gap: 18px;
  /* after the gap shorthand, which would otherwise reset it */
  row-gap: 10px;
}

.mcard-stats > div {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.mcard-stats .lbl {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}

.mcard-stats .val { font-size: 16px; white-space: nowrap; }

/* Followers and price are what a sponsor compares; per 1k is the check they
   do after, so it stays a number without competing with the two above it. */
.val-small { font-size: 12px; color: var(--muted); }
.mcard-stats .val.val-small { font-size: 12px; }

/* The gap is what keeps the sponsor's name and "Visit site" apart: a flex
   row drops the space between two links, and the card read "Bravo CoVisit
   site" without it (SEO batch, 2026-09-25). */
.mcard-foot { display: flex; justify-content: flex-end; gap: 16px; }

/* The site link on a sponsor card is a link, not the card's call to action:
   there is nothing to buy on that page, and a filled button asked to be
   pressed as if there were. Body size, muted, underlined. */
.mcard-foot a {
  font-size: 15px;
  color: var(--muted);
  text-decoration: underline;
}

.mcard-foot a:hover { color: var(--accent); }

/* A face on a card is bigger than the site's own 36px. */
.mcard .avatar { width: 44px; height: 44px; }

/* ------------------------------------------------------ sponsors listing */

/* Sponsor cards reuse the creator card wholesale. Two rules cover the only
   two differences: the company's @handle sits beside its name, and a budget
   range is a phrase rather than a number, so that one stat may wrap. */
.scard-handle { font-weight: 400; font-size: 13px; color: var(--muted); text-decoration: none; }

.scard-handle:hover { text-decoration: underline; }

.mcard-stats .val.wrapval { white-space: normal; }

/* The sponsor's own page (/sponsors/<slug>, SEO batch 2026-09-25) is the
   card, and the name in its head is the page's h1. It keeps the card name's
   face and sits one size up from it, with none of a page title's margin. */
.sponsorpage h1.rowhandle {
  font-family: inherit;
  font-size: 18px;
  letter-spacing: 0;
  margin: 0;
}

/* --------------------------------------------------- the directory's bar */

/* The toggle and the sort bar share one row above the card grid. Under
   640px the sort bar drops to its own line and the toggle keeps its size. */
.dir-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

/* The count inside a tab. The segmented control itself stays in base.css,
   because the dashboard wears one too; the count is written by dirBar() in
   views/shell.js, which only the home page and /sponsors call. */
.seg-count { font-weight: 400; color: var(--muted); }

.seg-active .seg-count { color: inherit; opacity: 0.8; }

/* ------------------------------------------------------- card tightening */

/* The badge slot is emitted even when there is nothing to say, so cards in a
   row line up whatever their state. */
.mcard-badge { flex: none; }

/* ------------------------------------------- home directory: three per row */

/* The directory keeps the wide card shape, so three per row needs a wider
   band than the 860px reading column. From 1320px up the home page (and
   /sponsors, which is the same shell) runs a 1240px band, which leaves each
   card about 389px: the same rectangle, three across. Below 1320px nothing
   changes and the grid stays two across. The header rule and the footer rule
   move with it so the wordmark starts where the first card does. */
@media (min-width: 1320px) {
  body.home main,
  body.home header.top,
  body.home footer { max-width: 1240px; }

  body.home .mgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The home page is one 940px column and this is its width: the hero, the
   proof line and 'How it works' all sit inside the same edges as the table,
   because seven columns spread wider than this are seven columns a reader's
   eye has to travel. body.home.wide main carries the matching outer number. */
.dirwrap { max-width: 940px; margin: 0 auto; }

/* The table sits in a panel. The box clips, so the header row's ground and
   the last row's tint are cut to the corner radius rather than squaring it
   off, and the wrapper needs no horizontal scroller of its own: the six
   columns are fitted to the width from 900 up and the row is two lines below
   it, so there is nothing to scroll. */
.dirbox { overflow: hidden; }

/* Section spacing: 80px between the blocks of the landing page on a desktop,
   48px on a phone. Written on the tops of the blocks under the hero, and the
   hero's own bottom margin is taken away, so one gap is one number and never
   two margins meeting. */
body.home.wide main > .hero { margin-bottom: 0; }

body.home.wide main > .proof,
body.home.wide main > .latest,
body.home.wide main > h2 { margin-top: 48px; }

@media (min-width: 700px) {
  body.home.wide main > .proof,
  body.home.wide main > .latest,
  body.home.wide main > h2 { margin-top: 80px; }
}

/* The hero's button to the top of the directory is its own number (prompt
   M1): the button and the table it points at are one move, and the sections
   under them are not.

   One number at every width, and a small one: 24 puts 64px between the row of
   faces and the directory's own switch at 1280 - the faces, the 8px step to
   the line that counts them, the line itself, the hero's own 8px of bottom
   padding and this - and 60px on a phone, where the hero's padding is 4. More
   than that reads as a break in the page rather than as one move. The hero's
   internal rhythm is untouched: this is the section's own top margin. */
body.home.wide main > .dirwrap { margin-top: 24px; }

/* The hero's button is an anchor to the directory's own id, so the press
   works with no script at all. Where the browser reads this, it glides. */
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

/* The one line of social proof, under the table. Not a heading and not a
   list: with one booking on it a heading makes a section out of a sentence. */
.proof {
  text-align: center;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 0;
}

/* Prompt Q1: recent activity is a panel, on the directory's own edges, with
   'Latest' in its head row. The box comes from base.css; what is written
   here is the width and what the rows are. */
.latest { max-width: 940px; margin-left: auto; margin-right: auto; }

/* The label in the panel's head row: quiet, and kept out of the centred
   page-heading style. The head row itself draws the padding and the rule. */
.latesthd {
  text-align: left;
  font-size: 13px;
  color: var(--muted);
  margin: 0;
  border: 0;
  background: none;
}

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

/* Prompt Q1: faces first, the sentence left-aligned straight after them, the
   age on the right. The sentence takes the room between the two, so a short
   event reads from the same left edge as a long one instead of floating in
   the middle of the row. The sides are the head row's 16. */
.latestrow {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 8px 16px;
  font-size: 13px;
  color: var(--ink-2);
}

.latestrow + .latestrow { border-top: 1px solid var(--hairline); }
.latesttxt { flex: 1 1 auto; min-width: 0; text-align: left; }
.latestwhen { flex: none; margin-left: auto; color: var(--muted); font-size: 12px; white-space: nowrap; }
.latesttxt a { color: var(--ink); text-decoration: none; }
.latesttxt a:hover { color: var(--accent); }

/* The directory as a table. Same bones as the public ledger, one row per
   listing, tighter type so the seven columns sit close enough to compare. */
.dirlist { font-size: 13px; }

/* Fixed layout so the columns are the same width on every sort: the widest
   handle in the list does not decide where the numbers sit. Every column is
   a percentage and the seven add up to 100 on purpose: fixed layout shares out
   whatever width is left over between the columns that claimed none, so a set
   that adds up to less than 100 buys a strip of dead table rather than a
   wider builder cell. Percentages only, no calc(): a calc() width is ignored
   outright in a fixed-layout column. The four figure columns are not equal
   either, because their headings are not: 'Per 1k followers' is the widest
   word in the table and 'Pin /mo' the narrowest. The two product prices took
   their share out of the builder cell rather than out of the other figures:
   the builder cell clips its bio with an ellipsis and loses nothing a reader
   needs, while a figure column that is a character too narrow wraps a price
   onto two lines. The second column is the face: the row draws one picture,
   in a cell of its own, and 5% of 940 is 47px, which is the 28px face and the
   cell's own padding either side. */
table.dirlist { table-layout: fixed; width: 100%; }

/* There are EIGHT cells on a row - the rank, the face and the six columns a
   reader compares - and every one of them carries a width that sums to 100.
   In a fixed-layout table a column with no width takes what is left over, and
   with the eight already summing to 100 there is none: a cell left out gets
   nothing and pushes its content past the table's right edge, which is a
   horizontal scrollbar on the box from about 1000px up.

   The four figure columns are cut to their own headings rather than shared
   out evenly - '$ / 1k followers' is the widest and 'Post' the narrowest -
   and the Builder cell takes the remainder, because it is the one cell that
   loses nothing by being narrower: it clips its bio with an ellipsis. */
.dirlist th:nth-child(1) { width: 4%; }
.dirlist th:nth-child(2) { width: 5%; }
.dirlist th:nth-child(3) { width: 28%; }
.dirlist th:nth-child(4) { width: 12%; }
.dirlist th:nth-child(5) { width: 13%; }
.dirlist th:nth-child(6) { width: 11%; }
.dirlist th:nth-child(7) { width: 10%; }
.dirlist th:nth-child(8) { width: 17%; }

/* The heart is a column, not a mark stuck to the last figure. It is the ninth
   cell and the only one measured in pixels - 32px is the button itself, so
   the column is the press and nothing more - and it is drawn only where the
   reader is signed in, so the eight above still sum to 100 and a signed-out
   table is unchanged. The horizontal padding comes off the cell: the 32 is
   the whole column. */
.dirlist th.rowsave { width: 32px; }

.dirlist td.rowsave {
  width: 32px;
  padding-left: 0;
  padding-right: 0;
  text-align: center;
  vertical-align: middle;
}

/* 4px over and under, so a 56px row holds the builder cell's two lines with
   room to spare: the handle and the bio come to 45px together, and 45 plus 8
   is under the 56 the row is held at. The rule only reaches the table itself;
   below 1024px base.css zeroes the padding at a higher specificity, where the
   row is a card and the cells are not drawn. */
.dirlist th,
.dirlist td { padding: 4px 8px; }

/* A row with a two-line cell in it centres everything beside that cell
   (prompt O1). The builder cell is a handle over a bio and fills the 56px
   row; every other cell on the row holds one line - the rank, the face, the
   three figures, the heart - and base.css gives a td vertical-align: top, so
   those single lines sat against the row's top edge while the cell next to
   them ran the full height. Middle is the line the row is read on, and the
   header row is left alone: a column label belongs on the rule it names.
   Below 1024px the row is a card and the cells are not laid out as cells, so
   this reaches the wide table only. */
.dirlist tbody td { vertical-align: middle; }

/* The headings are labels for the columns, not part of the reading: one step
   under the smallest thing in the rows. */
.dirlist th { font-size: 12px; }

/* Sorting belongs to the figures it changes. Plain header links keep it
   usable without script, while the quiet colours leave the rows in charge. */
.dirlist th a.colsort { color: var(--muted); text-decoration: none; display: inline-block; }
.dirlist th a.colsort:hover { color: var(--accent); }
.dirlist th a.colsort.on { color: var(--ink); }

/* Q2. One arrow per sortable heading, drawn always so that showing it costs
   the row no width, faint under the pointer on a column that is not open and
   solid on the one that is. Down is up turned over: the glyph never changes,
   so the change of direction is a half turn rather than one character being
   swapped for another. `is-turn` is what public/ui.js adds under the press,
   before the page it asks for comes back - the turn is the answer to the
   press, so it has to start with the press. A heading that is already down
   turns on to 360 rather than back to 0, so both directions turn the same
   way round. */
.colarrow {
  margin-left: 4px;
  display: inline-block;
  opacity: 0;
  transition: opacity var(--t-fast) ease-out, transform var(--t-fast) ease-out;
}

.dirlist th a.colsort:hover .colarrow { opacity: 0.4; }
.dirlist th a.colsort.on .colarrow { opacity: 1; }
.colarrow.colarrow-down,
.colarrow.is-turn { transform: rotate(180deg); }
.colarrow.colarrow-down.is-turn { transform: rotate(360deg); }

.dirlist td[data-label="#"] { color: var(--muted); }

/* A product a builder is not selling. The dash is a gap in a column of
   figures, so it steps back to the heading's colour rather than drawing the
   eye across the row. */
.dirlist .spotoff { color: var(--muted); }

/* The face cell. One per row, shared by the table and the card: the picture
   is drawn once in the markup and moved by the stylesheet, so a directory of
   thirty-one listings asks for thirty-one faces rather than sixty-two. */
.dirlist td.rowav { line-height: 0; }
.dirlist a.cellwho { color: var(--ink); text-decoration: none; }
.dirlist tbody tr:hover { background: var(--row-hover); }

/* The hover word only exists as a desktop pointer affordance. Keeping it out
   of the phone layout prevents a touch from leaving the hint behind. */
.rowgo { display: none; }

/* The builder's door keeps the directory's grid but steps back from listing
   content, so it reads as the way into the list rather than another result. */
.ghostav {
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: var(--hairline);
  display: block;
}

.ghostwho {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.ghostname { color: var(--muted); }
.ghostl2 { font-size: 12px; color: var(--muted); }
.ghostl2:hover { color: var(--accent); }
td.ghostcta { text-align: right; }

/* The last row of the directory: the builder's door, on the one accent wash
   the page has, so each tab ends in exactly one row. The tint is written on
   the row rather than on the cells, so the phone's stacked block carries it
   too. The hover twin keeps the ground still: this row does not open a
   listing, so it must not light up like one. */
.dirlist tbody tr.footrow,
.dirlist tbody tr.footrow:hover {
  background: #F1F2FB;
  background: color-mix(in srgb, var(--accent) 7%, #fff);
}

/* The same row on the sponsors tab, which is a grid of cards and has no
   table to end: one line of words, one filled button, the same ground.

   The press is the site's primary blue on both tabs, the same fill the hero
   button takes: this row is the one thing to do at the end of the table, so
   an outline would read as a second choice on a section that offers no first.
   It carries .btn-sm and nothing else, so the row keeps a row's height. */
.footblock {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  /* The sides are the table's own cell padding, not a block's. This row is
     the twin of the builders table's last row and sits on the same 940, so
     its words and its button start and end where that row's cells do: 8px in
     from the edge, the number .dirlist th/td take. At 16 both would sit a
     step further in than the row they answer. The 12 over and under is the
     block's own: it has no 56px row to fill. */
  padding: 12px 16px;
  margin-top: 16px;
  /* The row is a panel, so the border, the radius and the lift come from
     base.css. The tint is what this row is FOR - it is the one accent ground
     on the page - so it is written over --panel-bg here. */
  background: #F1F2FB;
  background: color-mix(in srgb, var(--accent) 7%, #fff);
}

.footblock .ghostbtn { flex: none; }

/* The press in that last row sits in the middle of it. A table cell inherits
   vertical-align: top, so on the wide table the button stood against the
   row's top edge while the words beside it ran two lines; the cell centres
   instead. The phone's stacked row is a flex line that already centres, and
   the sponsors tab's .footblock is one too, so this is the wide table only
   and the row keeps its height and its tint. */
.dirlist tbody tr.footrow td.ghostcta { vertical-align: middle; }

/* The phone's ordering control (prompt M1), above the table and only where
   the column headers are not drawn. One select, one button, one line. */
.dirsort {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 12px;
}

.dirsort-pick { flex: 1 1 auto; min-width: 0; height: var(--ctl-h); }

/* The landing page is not repainted (owner rule): base.css gave form controls
   the darker --control-line (paint7), and this one select keeps the hairline
   it has always had on the home page. Focus still takes the accent. */
body.home .dirsort-pick:not(:focus) { border-color: var(--hairline); }

/* The outline the page's other secondary presses take. base.css writes that
   for a link and for input[type=submit] and this is a <button>, which is what
   a form with one field submits with, so the same outline is written here
   rather than in a file this prompt does not own. The order is the reader's
   own choice and not the page's offer: a filled press here would be a second
   primary under the hero's one. */
.dirsort button.dirsort-go {
  flex: none;
  height: var(--ctl-h);
  padding: 0 16px;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}

@media (min-width: 900px) {
  .dirsort { display: none; }
}

/* The whole row is the link. There is one anchor in a row above 1024px, the
   builder cell's, and its ::after is stretched over the row: one tab stop,
   one link for a crawler, and a press anywhere on the row opens the listing.
   The row is the containing block, so the sheet has to say so.

   56px rows. The rule names the two rows that are not listings, because the
   ranking hairline is 8px of ground on purpose and the sponsors poster is a
   block of its own. */
@media (min-width: 900px) {
  .dirlist tbody tr { position: relative; }

  .dirlist tbody tr:not(.rankrule):not(.swrow) { height: 56px; }

  /* A listing takes the light accent ground on hover, while the rule, poster
     and builder door remain distinct from rows that open a listing. */
  .dirlist tbody tr:not(.rankrule):not(.swrow):not(.ghostrow):hover { background: var(--row-new); }

  /* The hint lives below its figure inside the $ / 1k cell, so it cannot
     cover the number and appears only while the row is under the pointer.
     The cell is named rather than taken as the last one (prompt R0): the last
     cell is the heart's column wherever a reader is signed in, and the hint
     belongs under the figure it points from. */
  .dirlist tbody tr:not(.rankrule):not(.swrow):not(.ghostrow) td.percell { position: relative; }

  .rowgo {
    display: block;
    position: absolute;
    right: 8px;
    bottom: 4px;
    font-size: 11px;
    opacity: 0;
    color: var(--muted);
    pointer-events: none;
    transition: opacity var(--t-fast) linear, color var(--t-fast) linear;
  }

  .dirlist tbody tr:hover .rowgo { opacity: 1; color: var(--accent); }

  .dirlist a.cellwho.rowstretch::after {
    content: '';
    position: absolute;
    inset: 0;
  }
}

/* The card's own cell. Two lines, one tap target, hidden on any screen wide
   enough for the columns themselves. The face is not in here: it is a cell of
   its own that shows in both shapes. The first line is who and how big, the
   second what each product costs a month. The handle is the only part of the
   first line that gives ground: everything either side of it keeps its width. */
td.rowline { display: none; }

.dirlist td.rowline .rowlink {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  color: var(--ink);
  text-decoration: none;
}

.dirlist td.rowline .rl1 {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.dirlist td.rowline .rlname {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The second line is the prices, and every one of them is the point of it, so
   the line takes a second row rather than dropping the last price into an
   ellipsis. It wraps whenever the cell is too narrow for one line, at any
   width. The rule this replaces asked the viewport instead and only fired
   under 360, which is not where the line runs out: a signed-in row carries
   the heart, so the cell is narrower than the screen by an amount the screen
   knows nothing about. Three products need 286px and the cell has 233 at 360,
   248 at 375 and 263 at 390, so "Post $15" was the price that went. */
.dirlist td.rowline .rl2p {
  min-width: 0;
  white-space: normal;
  /* Where the two lines are broken. A price belongs beside the product it is
     the price of, and the browser's own answer at 390 was to leave "$15" on a
     line by itself with "Post" on the one above. pretty is the rule against a
     last line that short, and it moves the break back to the separator; a
     browser that does not know the value wraps as it did and every price is
     still on the row. Under 340 the line is too narrow for either break to
     keep all three pairs whole. */
  text-wrap: pretty;
  color: var(--ink-2);
}

/* On a phone the builder door uses the same two-line summary as a listing,
   and keeps its outlined button visible at the row's right edge. */
@media (max-width: 899px) {
  table.stack.dirlist tr.ghostrow td.ghostcta { display: block; width: auto; flex: none; }
  table.stack.dirlist tr.ghostrow td.rowav .ghostav { width: 24px; height: 24px; }

  table.stack.dirlist tr.ghostrow td.rowline.ghostline {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
    width: auto;
    flex: 1 1 auto;
  }

  .dirlist td.rowline .ghostl1 { color: var(--muted); }
}

/* The tinyshelf badge, in the footer's Built by line. The badge is sized
   here to a height the credit line can carry; the credit wraps (base.css),
   so on a phone the badge drops to a line of its own instead of squeezing
   'Built by' onto two. */

.builtby .tinyshelf {
  display: inline-flex;
  flex: none;
  line-height: 0;
}

/* 162 by 48 is the badge's own 216 by 64 at three quarters, to the pixel, so
   nothing in the drawing is stretched. */
.builtby .tinyshelf img {
  width: 162px;
  height: 48px;
}

/* -------------------- the sign-up page and the joined line

   Signing up is a page and not a panel over the directory, so nothing here
   carries a width of its own, a backdrop or a close cross. */

.joinsub {
  margin: 0 0 24px;
  color: var(--muted);
  font-size: 13px;
}

.joinways {
  display: grid;
  gap: 12px;
  margin: 0 0 24px;
}

.joinways .btn,
.joinways input[type="submit"] { width: 100%; }

.joinway { margin: 0; }

.joinmail label { margin: 0 0 4px; }

.joinnote {
  margin: 8px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.joinprods {
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
  list-style: none;
}

.joinprod {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 13px;
}

.joinfrom {
  color: var(--muted);
  white-space: nowrap;
}

.joinquotes {
  margin: 24px 0 0;
  padding: 0;
  border-top: 1px solid var(--hairline);
  list-style: none;
}

.joinquote {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hairline);
  font-size: 13px;
}

.joinqw {
  color: var(--muted);
  white-space: nowrap;
}

.jointerms {
  margin: 24px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.jointerms a { color: var(--accent); }

.joinpanel {
  max-width: 560px;
  margin: 0 auto;
}

.joinform .jrow {
  display: grid;
  gap: 8px;
  padding: 12px 0;
  border-bottom: 1px solid var(--hairline);
}

/* Every field label one size and one colour (paint7): the two group labels
   over the radios ("Where you want to be", "Monthly budget") are spans, not
   labels, and were 13 against the labels' 15. */
.joinform .jlab,
.joinform .jlab-t {
  color: var(--ink);
  font-size: 15px;
}

.joinform .jlab label,
.joinform .jopt { margin: 0; }

.joinform .jctl > input,
.joinform .jctl > select { width: 100%; }

.joinform .verifiedhandle,
.joinform .jctl .check { margin: 0; }

/* The two tick boxes read like the radios above them (paint7): the same ink
   and the same size, where the site-wide label.check is muted and larger. */
.joinform .jctl .check { color: var(--ink); font-size: 14px; }

/* The page's one line under the h1, in the sans every other page's lede is
   set in (/results, /mcp), not the mono .sub (paint7). */
.joinlede {
  color: var(--muted);
  margin: 0 0 24px;
}

.jhint {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: 12px;
}

.joinform .jrow.jend { border-bottom: 0; }

.jradios { display: grid; }

.jopt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  font-size: 14px;
  cursor: pointer;
}

.jopt + .jopt { border-top: 1px solid var(--hairline); }

/* The radios take the brand like the tick boxes do (paint7). */
.jopt input[type="radio"] { accent-color: var(--accent); }

.jfav {
  display: block;
  margin-top: 8px;
  border-radius: 2px;
}

body.home .note.note-ok {
  max-width: 560px;
  margin: 0 auto 24px;
  padding: 12px 16px;
  color: var(--ink);
  background: var(--row-new);
  border-radius: var(--radius);
  font-size: 13px;
}

@media (min-width: 700px) {
  .joinform .jrow {
    grid-template-columns: 200px 1fr;
    align-items: start;
    gap: 24px;
  }

  .joinform .jlab { padding-top: 8px; }
}

/* ------------------------------- the budget chip on the directory (L3)

One press over the table, on the same line as the Builders / Sponsors switch
and to the left of it: the reader's eye lands on the filter first and on the
two sides of the market second. The bar itself is base.css's and keeps its
own space-between, so the switch stays left and the sort bar stays right
inside it; this row only puts the chip in front of the whole thing.

Below the wrap the chip takes the line above the switch rather than squeezing
in beside it: at 390px the two of them are wider than the column, and a chip
that has to shrink stops reading as a press. */
.dirhead {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 14px;
}

.dirhead > .dir-bar { flex: 1 1 auto; margin-bottom: 0; }

/* The empty anchor span that keeps old /#creators links landing (views/shell.js
   dirBar) is an inline element with no height, and outside a flex row it takes
   no space at all. Inside one it is a flex item and the gap counts it twice, so
   it is taken out of the layout here and lands exactly where it did before. */
.dirhead > span:empty { display: none; }

/* The same size, family and radius the segmented control beside it wears, so
   the two read as one row of controls rather than as a control and a badge.
   The padding is 10 and not 11 because this chip has a 1px border and the
   .seg-item beside it has none: 10 plus the border is the 11 the seg-item
   pads with, so the two are the same height and read as one row. At 11 the
   chip stood a pixel taller than the control it pairs with.
   On is the accent, which is the one filled thing on the page and says the
   table is not showing everything. */
.dirchip {
  flex: none;
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-decoration: none;
  padding: 10px 16px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  white-space: nowrap;
}

.dirchip:hover { color: var(--ink); text-decoration: none; }
.dirchip:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* On is the ink, not the accent. The segmented control beside it already
   wears the accent on the side you are reading, and two filled blue pills in
   a row read as two of the same thing; the ink says "this one is switched on"
   without competing with the switch or with the page's one blue press. */
.dirchip-on,
.dirchip-on:hover {
  color: var(--ground);
  background: var(--ink);
  border-color: var(--ink);
}

/* ------------------------------------ the sponsor card's own mark (L3)

A favicon is a logo, not a face: it keeps its own shape inside the 44px the
card gives every other mark, and it is not cropped to a circle. The ground
under it is the card's own, so a transparent PNG has something to sit on. */
.mcard .scard-fav {
  border-radius: 8px;
  object-fit: contain;
  padding: 2px;
  background: var(--surface);
}

/* ------------------------------------------- the heart (prompt N4)

   The directory draws the heart and does not load app-shell.css, so the rules
   it needs are here, in this page's own file. A button and never a link, and
   a button that looks like the text beside it: no ground, no border, the size
   of the row's own numbers, and the one green the site keeps for a thing that
   is saved.

   It has a column of its own from 900 up and rides inside the one-line cell
   below that, so a saved row is exactly as tall as an unsaved one either way.
   The whole row is one stretched link, so
   the heart is lifted above that overlay rather than left under it, which is
   what makes it pressable at all. */
.heartform { display: inline; margin: 0; }

button.heart {
  padding: 0 4px;
  text-decoration: none;
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  background: none;
  border: 0;
  cursor: pointer;
}

button.heart.on { color: var(--saved); }
button.heart:hover { color: var(--ink); }
button.heart.on:hover { color: var(--saved); }

/* Two hearts are written on a row, one for each shape of it, and exactly one
   is ever drawn: the columns' heart is in the .rowsave cell, which is not
   drawn below 900, and this one is the other way round. */
.rowheart-line { display: none; }

@media (max-width: 899px) {
  /* The one-line cell holds the two-line link and the heart side by side, so
     the heart is at the row's right edge and the row is two lines rather than
     three. */
  table.stack.dirlist td.rowline {
    display: flex;
    align-items: center;
    gap: 8px;
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
  }

  .dirlist td.rowline .rowlink { flex: 1 1 auto; min-width: 0; }

  .dirlist td.rowline .rowheart-line {
    display: block;
    flex: none;
    position: relative;
    z-index: 1;
  }
}

@media (min-width: 900px) {
  /* The heart is above the row's stretched link, so the press saves rather
     than opening the listing. It has a column of its own, so the $ / 1k
     figure is a block like every other figure on the row and nothing has to
     share its line. */
  .rowheart { position: relative; z-index: 1; line-height: 1; }
}

/* The card below 900px is the one-line cell and the face, and the heart it
   carries is the one inside that cell. The column's own cell is a column and
   has nothing to draw here. */
@media (max-width: 899px) {
  .dirlist td.rowsave,
  .dirlist th.rowsave { display: none; }
}

/* ================================================== social proof (prompt O6)

   Faces, not counts alone. Every picture on this page is one the site already
   holds: the avatar cache's copy of an X picture, or a favicon prompt L3
   stored on the sponsor's row. Nothing is fetched while a page is drawn and
   nothing is drawn for a handle the site has no face for beyond its own
   initial, which is what the twin span behind every picture prints. */

/* Prompt P1: one row of faces under the button, and one line under the row.
   Builders first, biggest audience first, then a small gap and the sponsors'
   marks. It is a flex box and not a paragraph: the two groups sit on one
   line and they wrap together on a narrow phone.

   The row is a span and not one link to the table: each builder's face is
   its own link to that builder's page, and an anchor cannot hold anchors. The
   sponsors' marks stay plain, because a sponsor has no page here. Both wear
   the .tip label (base.css) that names the handle, which is the browser's own
   title bubble without the wait. */
.herofacerow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  margin: 16px 0 0;
  text-decoration: none;
}

.herofaces { display: inline-flex; align-items: center; }

/* The sponsors' group starts a new stack, so its first mark is not pulled
   under the last builder: the 4px gap above is the whole distance between
   them. */
.herofacesp { margin-left: 4px; }

/* The line under the row. Muted, 14px, and printed once: the number is the
   number, so nothing counts up to it. Its line box is pinned to 24px, a
   number on the page's own scale, so the distance from the faces to the
   directory under them is four whole steps rather than the 61.7px an
   inherited leading comes to. */
.herocount {
  margin: 8px 0 0;
  font-size: 14px;
  line-height: 24px;
  color: var(--muted);
}

.avatar.herofa {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* The ring is the page's own colour, so the overlap reads as one face in
     front of another rather than as two circles touching. */
  border: 2px solid var(--ground);
  box-sizing: border-box;
  vertical-align: middle;
}

.avatar.herofa.small { width: 28px; height: 28px; }

/* The ring is the page's colour, which is also the letter twin's background,
   so a face with no picture would have no edge at all. The twin draws its own
   hairline inside the ring instead of taking the ring's colour, which would
   put a hairline between two overlapping photographs. */
.avletter.herofa { box-shadow: inset 0 0 0 1px var(--hairline); }

/* The overlap, and the one exception base.css already writes for the profile
   stack: a letter cannot be overlapped, because ten pixels over a glyph drawn
   in the middle of its circle covers the glyph. A hidden twin takes no room at
   all ([hidden] is display:none), so the walk is over what is showing and a
   stack of real pictures lays out exactly as it would without this. */
.herofaces > * + * { margin-left: -10px; }

/* The pair sits inside a .tip, so the element the overlap is written on is
   the wrapper rather than the picture. The exception is the same one, asked
   one level up: a face that fell back to its letter is not overlapped and
   does not overlap the face after it. */
.herofaces > .tip:has(img[data-letter][data-fail]),
.herofaces > .tip:has(img[data-letter][data-fail]) + .tip { margin-left: 2px; }
.herofaces > .tip:first-child { margin-left: 0; }

.herofaces img[data-letter][data-fail] + .avletter,
.herofaces img[data-letter][data-fail] + .avletter + .avatar { margin-left: 2px; }
.herofaces img.avatar:first-child + .avletter { margin-left: 0; }

/* A phone takes the same row one size down: eight faces at 28px still fit
   across 390px with the overlap, and the line under them does not change.

   THE NINTH FACE IS THE SEVENTEENTH CHILD. Every face is a pair, the picture
   and the letter twin behind it, so the walk counts in twos: hiding from the
   17th child on drops faces nine to twelve whole, picture and twin together,
   and never leaves a twin behind to be revealed by public/avatar.js when its
   picture 404s. The sponsors' group is left alone; it is four marks at most. */
@media (max-width: 560px) {
  .avatar.herofa { width: 28px; height: 28px; }
  .avatar.herofa.small { width: 24px; height: 24px; }
  /* Both halves of the pair are named rather than written as one `> *`:
     base.css turns a fallen-back twin on with img[data-letter][data-fail] +
     .avletter:not([hidden]), and a `*` here loses to it on specificity and
     leaves the letter showing. */
  .herofacerow .herofaces:not(.herofacesp) > img:nth-child(n+17),
  .herofacerow .herofaces:not(.herofacesp) > .avletter:nth-child(n+17) {
    display: none;
  }

  /* With the pair inside one .tip the walk counts in ones, so the ninth face
     is the ninth child. The rule above stays for a row this file has not
     wrapped in a .tip. */
  .herofacerow .herofaces:not(.herofacesp) > .tip:nth-child(n+9) { display: none; }
}

/* One line under the hero while something is actually up, and no line at all
   otherwise: the server prints it only when a run covers today. */
.livenow {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 16px 0 0;
  font-size: 12px;
  color: var(--muted);
}

.livedot {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.livefaces { display: inline-flex; align-items: center; flex: none; }

.avatar.livefa {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--surface);
  box-sizing: border-box;
}

.livefaces > * + * { margin-left: -8px; }
.livefaces img[data-letter][data-fail] + .avletter,
.livefaces img[data-letter][data-fail] + .avletter + .avatar { margin-left: 2px; }
.livefaces img.avatar:first-child + .avletter { margin-left: 0; }

.livetxt a { color: var(--ink); text-decoration: none; }
.livetxt a:hover { color: var(--accent); }

/* The two sides of the event, sponsor then builder, in a track of their own
   at the head of the row. The row's own baseline alignment would hang the
   faces off the first line of a wrapped sentence, so the pair is centred on
   the row instead and the words keep the baseline. */
.latestrow { align-items: center; }

/* Prompt R0: the track is the width of two overlapping faces whatever the
   row holds, so a listing event with one face starts its sentence on the
   same x as a booking with two and the strip reads down a single left edge.
   32px is the pair: a 20px face, then the second one 8px under it. */
.latestfaces {
  flex: none;
  display: inline-flex;
  align-items: center;
  width: 32px;
}

.avatar.latestfa {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--surface);
  box-sizing: border-box;
}

/* One overlap, whichever of the two the face fell back to. A pair standing
   2px apart wherever a picture failed would make the track 44px on those rows
   and 32px on the rest, which is the very thing the reserve above is for. A
   letter carries the same 1px surface ring a picture does, so the stack reads
   as two discs either way and the track is 32px on every row. */
.latestfaces > * + * { margin-left: -8px; }
.latestfaces > .tip:first-child { margin-left: 0; }
.latestfaces img[data-letter][data-fail] + .avletter,
.latestfaces img[data-letter][data-fail] + .avletter + .avatar { margin-left: 2px; }
.latestfaces img.avatar:first-child + .avletter { margin-left: 0; }

/* The pitch on a sponsor card (prompt O7). A <details> and not a script, so
   it opens with JavaScript off; closed it is one line the height of a link,
   which is what keeps a grid of cards a grid rather than a column of forms. */
.mcard .pitchbox { border-top: 1px solid var(--hairline); margin-top: 8px; padding-top: 8px; }
.mcard .pitchbox > summary {
  cursor: pointer;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--accent);
}
.mcard .pitchbox textarea { width: 100%; margin-top: 8px; }
.mcard .pitchbox .actions { margin-top: 8px; }

/* ============================================= Q2: the restrained set

   Five things move on this page and nothing moves on its own. Every one of
   them is timed by --t-fast (150ms) and eased out, and --t-fast is zero under
   the reduced-motion block at the top of base.css, so all of it goes away at
   once for a reader who asked for less. The reduced-motion block in this file
   zeroes what a token cannot. */

/* 1. The row. The tint and the ghost "Sponsor ->" at the right edge are the
      .rowgo rule above; this adds the fade between the two grounds and says
      out loud what the stretched link over the row makes true, that the whole
      row is the press. */
.dirlist tbody tr { transition: background var(--t-quick) ease-out; }

@media (min-width: 900px) {
  .dirlist tbody tr:not(.rankrule):not(.swrow):not(.ghostrow) { cursor: pointer; }
}

/* 4. The press. 6% darker under the pointer and a pixel down while it is
      held, which is the whole of it: this page's buttons are one filled blue
      and one outline, and a shadow under either would be a second box on a
      page Q1 just gave one box rule. brightness() rather than a colour is
      what keeps it to one number and adds no hex to the palette.

      The ring is the brand at full strength, and only for a keyboard:
      :focus-visible is what a browser says when the reader did not arrive by
      pointer. The accent at 40% reads as a smudge on this ground, which is no
      use to the one reader who needs it. */
body.home .btn,
body.home button,
body.home input[type="submit"] {
  transition: filter var(--t-fast) ease-out, transform var(--t-fast) ease-out;
}

body.home .btn:hover,
body.home button:hover,
body.home input[type="submit"]:hover { filter: brightness(0.94); opacity: 1; }

body.home .btn:active,
body.home button:active,
body.home input[type="submit"]:active { transform: translateY(1px); }

body.home .btn:focus-visible,
body.home button:focus-visible,
body.home input[type="submit"]:focus-visible,
body.home a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Nothing that moves, for a reader who asked for none of it. The three time
   tokens are already zero by then, so this is only the two transforms and
   the arrow's turn, which are states rather than durations. */
@media (prefers-reduced-motion: reduce) {
  .colarrow,
  .dirlist tbody tr,
  body.home .btn,
  body.home button,
  body.home input[type="submit"] {
    transition: none;
  }

  body.home .btn:active,
  body.home button:active,
  body.home input[type="submit"]:active { transform: none; }
}
