/* sponsormyprofile. Light page mixing the NoPlanB layout (centered hero, one
   input, one blue button, clean table) with the TrustMRR data language
   (monospace numbers, small caps labels, dense rows). Warm ground, ink text,
   hairlines, one blue accent. No gradients, no shadows. */

@font-face {
  font-family: "DM Sans";
  src: url("/fonts/DMSans-Regular.f3f6e717.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

/* The two middle weights. Thirty-odd rules across these stylesheets ask for
   500 or 600, and without these two faces a browser has nothing to answer
   with: it snaps 500 down to the regular and 600 up to the bold, so the
   weight a rule asks for is not the weight that draws. Both are the
   same latin subset the Regular and the Bold are, instanced off the same
   variable source at the same version, so they sit on the same metrics. */
@font-face {
  font-family: "DM Sans";
  src: url("/fonts/DMSans-Medium.d6502416.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DM Sans";
  src: url("/fonts/DMSans-SemiBold.15864301.woff2") format("woff2");
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DM Sans";
  src: url("/fonts/DMSans-Bold.c2befb59.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Space Mono";
  src: url("/fonts/SpaceMono-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Space Mono";
  src: url("/fonts/SpaceMono-Bold.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --ground: #F5F5F7;
  --surface: #FFFFFF;
  --ink: #111111;
  /* One step in from the ink, for the second line of a two-line cell: the
     bio under a handle is context, not a label, so it steps down without
     going as far as --muted, which is what a column heading takes. */
  --ink-2: #5C5C5C;
  --muted: #66666E;
  --hairline: #E3E3E8;
  /* The edge of a form control (paint7): an input, a textarea, a select. The
     hairline is 1.3:1 on white, which is a divider and not a field; this is
     3.3:1, the least a control's edge needs to be seen. Dividers keep the
     hairline. */
  --control-line: #8C8C94;
  --row-hover: #EDEDF1;
  /* The ground a row that has just arrived holds for two seconds. Light
     enough to read black text on, blue enough to say "this one is new". */
  --row-new: #E4EAFB;
  --accent: #3048C8;
  --on-accent: #FFFFFF;
  --error: #B42318;
  --sans: "DM Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Space Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  /* Dashboard and admin design system (2026-08-29). Sizes, one control
     height, one radius and the two column widths those pages share. The public
     sheets read them too: landing.css takes --ctl-h and --radius, and
     public-profile.css takes both of those plus the type sizes. */
  --h1: 28px;
  --h2: 18px;
  --body: 15px;
  --help: 13px;
  --eyebrow: 11px;
  /* One control height for the whole site: an input, a select and a button
     are the same 40px, so a row of them lines up on both edges. */
  --ctl-h: 40px;
  --row-h: 44px;
  --radius: 8px;
  --danger: #C0392B;
  --saved: #1E7B4B;
  --content-max: 880px;
  /* Prompt T0 (2026-08-30). What the dashboard and the public builder page
     are drawn with. The spacing scale itself (4, 8, 16, 24, 32, 48) is
     written out in full inside the T0 region rather than hidden behind six
     aliases, because scripts/design-lint.js checks every px value in that
     region and a var() would hide the number from the check. */
  --btn: 14px;
  --label: 13px;
  --table: 15px;
  --radius-card: 12px;
  --radius-pill: 999px;

  /* Prompt Q1 (2026-09-04). The one box on this site. Every container is
     drawn by `.panel` from these four values, so no page has to draw a
     border, a radius and a ground of its own again.

     --line is an alias of --hairline: the prompt names the token --line and
     the file has always called it --hairline. Aliasing keeps one colour and
     one name for it in the panel rules without renaming five hundred uses.

     16 for the radius, not 24: tables and calendars sit inside these boxes
     and a 24 corner rounds their own corners off. */
  --line: var(--hairline);
  --panel-bg: #fff;
  --panel-border: 1px solid var(--line);
  --panel-radius: 16px;
  --panel-shadow: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px -12px rgba(0, 0, 0, 0.10);
  --line-max: 560px;
  --dashnav-w: 200px;
  --profile-max: 980px;
  /* The inbox's three columns (prompt Y1), declared here beside the other
     two column widths for the same reason those are: a track width is a
     layout dimension rather than a step of the spacing scale, and the
     dashboard shell already reads --dashnav-w and --content-max exactly this
     way. --ibx-max is the width the thread page's own shell may take, wider
     than a reading column because it holds three of them. */
  --ibx-list: 320px;
  --ibx-rail: 300px;
  --ibx-max: 1400px;
  --t-fast: 150ms;
  --t-mid: 200ms;
  --t-slow: 300ms;
  /* The press and the row ground (motion section at the end of this file):
     quicker than --t-fast, because they answer a pointer that is already
     there rather than arriving. Zeroed with the other three below. */
  --t-quick: 120ms;
  color-scheme: light;
}

/* A reader who has asked for less movement gets none: the three motion
   tokens are what every transition and the board's arrival animation are
   timed by, so zeroing them here turns all of it off at once. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --t-fast: 0s;
    --t-mid: 0s;
    --t-slow: 0s;
    --t-quick: 0s;
  }
}

* { box-sizing: border-box; }

/* The hidden attribute wins over any class in this file. Without it a class
   that sets display (.help does) would show an element the server marked
   hidden, which is how the card-fee sentence under the spot form is kept
   out of the way while the box is ticked. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--ink);
}

body {
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  -webkit-text-size-adjust: 100%;
}

.mono { font-family: var(--mono); }
.center { text-align: center; }

/* ---------------------------------------------------------------- header */

header.top {
  max-width: 960px;
  margin: 0 auto;
  padding: 18px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--hairline);
}

body.wide header.top { max-width: 1100px; }

.wordmark {
  font-family: var(--mono);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: 9px;
}

.wordmark img { width: 20px; height: 20px; flex: none; }

header.top nav { display: flex; gap: 20px; }

header.top nav a {
  font-family: var(--mono);
  color: var(--muted);
  text-decoration: none;
  font-size: 14px;
}

header.top nav a:hover { color: var(--accent); }

/* ------------------------------------------------------------------ main */

main {
  max-width: 640px;
  margin: 0 auto;
  padding: 40px 20px 24px;
}

body.home main { max-width: 860px; }
body.wide main { max-width: 1100px; }

/* The prose pages (About, How it works, Rules, Terms, Privacy) are one text
   column and no box, and prompt Q3 puts that column's left edge on the
   wordmark's rather than in the middle of the page.

   main takes the header's own width, so the two content boxes are the same
   box: the same max-width, the same 20px of side padding, both centred, so
   their left edges are the same number at every width, phone included. The
   column inside keeps the 640px reading measure and sits at the left of it.

   The hook is the wrapper the page's own markup opens with, not a body
   class: layout() writes <body> and <main> for every page on the site, and a
   page that does not draw a prose column - /request, /worth, the gate pages -
   has no .prose in it and does not move. */
main:has(> .prose) { max-width: 960px; }
.prose { max-width: 640px; }

/* The one table a prose page draws (How it works' three products) has to fit
   the reading column rather than scroll inside it: cells are nowrap site-wide
   because every other table on the site holds a figure or a handle, and this
   one holds sentences. Fixed layout gives the five columns an equal share of
   whatever the column is wide, and the text wraps inside them. */
.prose table { table-layout: fixed; }
.prose th,
.prose td { white-space: normal; overflow-wrap: break-word; }

/* How it works' three products (paint7). Five columns of sentences wrapped
   to five lines a cell on a wide screen and to a label-and-value list on a
   phone, so each product is one row: its name as a small mono heading and
   what you get beside it, then one muted line with the three facts (priced,
   how it is marked live, the refund rule). The same at every width. */
.howprods { padding: 0 20px; }
.howprod { padding: 16px 0; }
.howprod + .howprod { border-top: 1px solid var(--hairline); }
.howprodhead { margin: 0; }
.howprodname {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 15px;
  margin-right: 8px;
}
.howprodfacts { margin: 4px 0 0; font-size: 14px; color: var(--muted); }

/* The footer (2026-09-25): four columns of links, then the Built by line
   under a hairline of its own. Four across from 600 up, two across below,
   and the credit always has a line to itself, so "Built by" never breaks. */
footer {
  max-width: 960px;
  margin: 40px auto 0;
  padding: 40px 20px 48px;
  color: var(--muted);
  font-size: 14px;
  border-top: 1px solid var(--hairline);
}

footer a { color: var(--muted); }
footer a:hover { color: var(--accent); }

/* Columns of links read as a list, so they carry no underline at rest. The
   one they show under the pointer is drawn from the left by the motion
   section at the end of this file. */
.footcol a { text-decoration: none; }

.footlinks {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 24px;
}

.footcol {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
}

.foothead {
  margin: 0 0 4px;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.footbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 16px;
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--hairline);
}

@media (max-width: 600px) {
  .footlinks { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 16px; }
}

h1 {
  font-family: var(--mono);
  font-size: 30px;
  font-weight: 700;
  line-height: 1.2;
  margin: 0 0 12px;
  letter-spacing: -0.02em;
}

h2 {
  font-family: var(--mono);
  font-size: 19px;
  font-weight: 700;
  margin: 48px 0 14px;
  letter-spacing: -0.01em;
}

p { margin: 0 0 16px; }

.sub {
  font-family: var(--mono);
  font-size: 16px;
  color: var(--muted);
  margin-bottom: 28px;
}

.rule {
  font-size: 13px;
  color: var(--muted);
  margin-top: 12px;
}

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

/* Two link styles for two destinations. A link that stays on the site is the
   default one; a link that leaves it carries the arrow, printed by the
   stylesheet so no view has to spell the character and no two of them can
   spell it differently. inline-block keeps the anchor's underline off the
   mark, and the mark is drawn, not read: aria-hidden is not available on a
   pseudo-element and the link's own words already say where it goes. */
a.ext::after {
  content: "\2197";
  display: inline-block;
  margin-left: 4px;
  text-decoration: none;
}

/* The footer credit is the one place the mark sits at the end of a flex row
   that has no room left at 390: the handle fills the line and the arrow, an
   inline-block of its own, wraps under it and doubles the line's height.
   Holding the anchor on one line moves the whole thing down together. */
.builtby a.ext {
  white-space: nowrap;
}

a.btn, button.btn, input[type="submit"] {
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  border: 0;
  border-radius: 8px;
  padding: 12px 22px;
  font-size: 15px;
  font-family: var(--mono);
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
}

a.btn:hover, button.btn:hover, input[type="submit"]:hover { opacity: 0.92; }

/* The second press of a pair, at global scope. The dashboard and the builder
   page carry this look under body.dash and body.profile, and the token page
   /r/<token> carries no body class at all: a press there either takes the
   primary fill above or falls back to raw browser chrome. This is the same
   outline, reachable from a page with no class on its body. The 1px border is paid for out of the
   padding, so a secondary press is exactly as tall as the primary beside it.
   No hover rule of its own: the fade above already answers the pointer, and
   the dashboard's own hover rules stay the only ones on those pages.

   A LINK press takes it too: the question's own /r page offers the request
   form as an anchor, and without this selector that anchor reads as a second
   primary on a page that already has one. */
a.btn.secondary,
input[type="submit"].secondary {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--hairline);
  padding: 11px 21px;
}

/* --------------------------------------------------------------- add row */

.addrow {
  display: flex;
  gap: 10px;
  justify-content: center;
  max-width: 480px;
  margin: 0 auto;
}

body:not(.home) .addrow { margin: 0; justify-content: flex-start; }

.addrow input {
  flex: 1;
  min-width: 0;
  padding: 12px 14px;
  font-size: 17px;
  font-family: var(--mono);
  color: var(--ink);
  border: 1px solid var(--control-line);
  border-radius: 8px;
  background: var(--surface);
}

/* A field and its button are one row, so they are one height: the same 40px
   every other control on the site takes. */
.addrow .btn,
.addrow button.btn,
.addrow input[type="submit"] {
  height: var(--ctl-h);
  padding: 0 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

@media (max-width: 480px) {
  .addrow { flex-direction: column; }
  /* In a column, flex: 1 is a height of 0 grown to the text, so the field
     came out 23px under a 40px button. It takes the control height here. */
  .addrow input { flex: none; height: var(--ctl-h); }
}

/* A field the server sent back with an error (/worth, /compare; paint7). The
   edge takes the error colour and keeps it: it goes when the page does, not
   on a timer, because the sentence above it is still true. The row shakes
   once as the page arrives, four pixels each way, and not at all for a
   reader who asked for less motion. */
.addrow.is-error input[type="text"] { border-color: var(--error); }

@keyframes fieldshake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(-4px); }
  40%, 80% { transform: translateX(4px); }
}

@media (prefers-reduced-motion: no-preference) {
  .addrow.is-error input[type="text"] { animation: fieldshake var(--t-slow) ease-in-out 1; }
}

/* ------------------------------------------------ rows, faces and badges */

a.btn.small { padding: 8px 16px; font-size: 15px; }

.rowmain {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--ink);
  text-decoration: none;
}

.rowname { display: flex; flex-direction: column; min-width: 0; }

.rowhandle { font-weight: 700; }

.rowblurb {
  color: var(--muted);
  font-size: 13px;
  max-width: 340px;
  overflow: hidden;
  text-overflow: ellipsis;
}

.avatar {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--ground);
  flex: none;
}

.avatar.big { width: 64px; height: 64px; }

/* The face that did not arrive.

   Every avatar is served from our own cache (/media/avatar/@handle.png). When
   there is nothing to serve the route answers 404 with an empty body, the
   browser fires the img's own error event, and public/avatar.js puts data-fail
   on the picture, hides it, and shows the twin span the server wrote beside
   it with the first letter of the handle in it.

   The twin carries the same classes as the picture, so every size, every
   overlap and every grid cell the stylesheet already has for a face applies
   to it without being written twice.

   Which of the two is showing rides on the hidden attribute, and [hidden] at
   the top of this file is the one !important the site has: no rule anywhere,
   however deep, can show the twin while the picture is on its way or leave
   the broken picture showing once it is not. The selector below is four deep
   for the same reason in the other direction: the twin borrows the picture's
   classes, and one of the rules those match (body.profile .bpic .avatar) sets
   a display of its own that would otherwise beat this one.

   No new colour: the site's own ground with the heading grey on it, in the
   mono the rest of the small type uses. */
.avletter {
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  overflow: hidden;
  background: var(--ground);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1;
  text-align: center;
}

img[data-letter][data-fail] + .avletter:not([hidden]) { display: inline-flex; }

.avatarhead { margin-bottom: 16px; }

.badge {
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  border-radius: 999px;
  padding: 3px 10px;
  border: 1px solid var(--hairline);
  color: var(--muted);
}

.badge.open { border-color: var(--accent); color: var(--accent); }

/* The public bookings ledger. Same bones as an admin table, quieter: no
   actions, so every column is short and the dates line up as numbers. */
.ledger { font-size: 14px; }
.ledger td.wrapcell { min-width: 0; max-width: 320px; }
.ledger td[data-label="Status"] { color: var(--muted); }
.ledger td[data-label="When"] { font-family: var(--mono); font-size: 13px; color: var(--muted); }
.ledger td[data-label="Price"] { font-family: var(--mono); }

@media (max-width: 700px) {
  /* The stacked card puts the label on the left, so the mono date does not
     have to hold a column width. */
  .ledger td[data-label="When"] { font-size: 12px; }
}

.steps { margin: 0 0 16px; padding-left: 22px; max-width: 640px; }
.steps li { margin-bottom: 10px; }

/* ----------------------------------------------------------------- forms */

label {
  display: block;
  margin: 18px 0 6px;
  font-size: 15px;
}

label .hint {
  display: block;
  color: var(--muted);
  font-size: 13px;
}

label.check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin: 24px 0 6px;
  color: var(--muted);
}

label.check input { width: auto; margin-top: 4px; accent-color: var(--accent); }

input[type="text"], input[type="email"], input[type="number"], input[type="date"],
input[type="search"], textarea, select {
  width: 100%;
  padding: 10px 12px;
  font-size: 16px;
  font-family: inherit;
  color: var(--ink);
  border: 1px solid var(--control-line);
  border-radius: 8px;
  background: var(--surface);
}

/* One height for every single-line control on the site, so a label above an
   input and a button beside it read as one row rather than three sizes.
   textarea is deliberately out of the list: it is the one control whose
   height is the point. box-sizing is border-box for everything (see * above),
   so the padding is inside the 40px. */
input[type="text"], input[type="email"], input[type="number"], input[type="date"],
input[type="search"], select {
  height: var(--ctl-h);
  padding-top: 0;
  padding-bottom: 0;
}

input:focus, textarea:focus, select:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

textarea { min-height: 80px; resize: vertical; }

form .actions { margin-top: 28px; }

.error {
  border: 1px solid var(--error);
  color: var(--error);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 20px;
  font-size: 15px;
  background: var(--surface);
}

.code {
  font-family: var(--mono);
  font-size: 22px;
  border: 1px solid var(--hairline);
  border-radius: 8px;
  background: var(--surface);
  padding: 14px 16px;
  display: inline-block;
  margin-bottom: 20px;
}

.facts { border-top: 1px solid var(--hairline); margin: 24px 0; }

.facts div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  border-bottom: 1px solid var(--hairline);
  padding: 10px 0;
}

.facts span:first-child { color: var(--muted); }

.price { font-family: var(--mono); font-size: 28px; margin: 0 0 16px; }

.blurb { color: var(--muted); }

/* A named group inside a dashboard form. The page's sections are h2, so a
   group inside one listing block sits a size under that. */
.formhead {
  font-size: 15px;
  font-weight: 700;
  margin: 26px 0 0;
}

/* Withdrawing a link request. The button sits on the line that names the
   request, so it reads as part of the sentence rather than as an action of
   its own. Its form is written after the links form, because a form inside a
   form is not HTML; nothing about it is meant to be seen. */
.cancelform { display: none; }

.linkcancel {
  font-family: inherit;
  font-size: 12px;
  padding: 2px 8px;
  margin-left: 6px;
  vertical-align: baseline;
  border: 1px solid var(--hairline);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}

.linkcancel:hover { border-color: var(--accent); color: var(--accent); }

.badgeprev img { display: block; max-width: 100%; }

.cardprev img {
  display: block;
  width: 100%;
  max-width: 600px;
  height: auto;
  border: 1px solid var(--hairline);
  border-radius: 10px;
}

/* The two ways to put the card somewhere: one click into an X composer, and
   one snippet for a page that is not X. Side by side above 700px, because they
   are alternatives rather than steps; stacked below it, where the snippet
   needs the whole column to be readable. The button column is only as wide as
   the button. */
.sharegrid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 4px 28px;
  align-items: start;
}

@media (min-width: 700px) {
  .sharegrid { grid-template-columns: max-content minmax(0, 1fr); }
}

/* The label starts on the same line as the button next to it, so the two
   halves of the row read as one row rather than as two things that happen to
   be side by side. */
.embedbox label { margin-top: 0; }

.avbadge img {
  display: block;
  max-width: 100%;
  height: auto;
}

.avbadgesmall img { width: 48px; height: 48px; }

/* .hint is only styled inside a label, and this one is not in one. */
.avbadgesmall .hint {
  display: block;
  margin: 0;
  color: var(--muted);
  font-size: 13px;
}

/* The two rings, side by side, because the whole point is the comparison: the
   plain one is easier to recognise small, the one with the price says more
   when somebody stops. A column is 240 + 20 + 48 = 308px wide, so they go one
   above the other well before a phone. */
.avgrid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px 28px;
  margin: 4px 0 0;
}

@media (min-width: 700px) {
  .avgrid { grid-template-columns: 1fr 1fr; }
}

/* ---------------------------------------------------------------- tables

   The admin's tables and the two public ones: the 51-page census draws
   .table-wrap and table.stack on /admin, /admin/deals, /admin/sponsors,
   /admin/badges, /how-it-works and the landing page. */

/* Admin tables are wider than a phone. The wrapper scrolls, so the page does
   not. Nothing inside a .table-wrap is sticky, so the scroll container costs
   nothing. */
.table-wrap { overflow-x: auto; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  background: var(--surface);
  border: 1px solid var(--hairline);
}

th, td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: top;
  white-space: nowrap;
}

td.wrapcell { white-space: normal; min-width: 220px; max-width: 340px; overflow-wrap: anywhere; }

th { color: var(--muted); font-weight: 700; }

td form { display: inline; }

/* A button in a table cell only ever happens inside body.dash or body.admin
   (checked: no public page renders one), so its look is not here: the
   dashboard's is in this file's T0 region and the admin's is in
   app-shell.css. */

.empty { color: var(--muted); font-size: 15px; }

/* ------------------------------------------------------------- dashboard */

.stepline { font-size: 12px; margin: 0 0 8px; }

.step.done { color: var(--accent); }
.step.todo { color: var(--muted); }
.step.off { color: var(--muted); }
.steparrow { color: var(--muted); padding: 0 8px; }

.dealpost {
  border-left: 3px solid var(--hairline);
  padding-left: 12px;
  margin: 10px 0;
  font-size: 15px;
}

.dealpost.empty { color: var(--muted); }

/* Holds a long private deal link without pushing the page sideways. */
.linkbox { overflow-wrap: anywhere; }

input[type="file"] { font-size: 14px; font-family: inherit; }

/* --------------------------------------------------- admin compact cells */

.cellsub {
  display: block;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--sans);
  font-weight: 400;
}

.verifiedhandle { font-size: 18px; margin: 4px 0 0; }

/* ---------------------------------------------------------- agreement */

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

.agrlist { margin: 12px 0 24px; padding-left: 20px; }
.agrlist li { margin-bottom: 8px; font-size: 15px; }

.unsigned { color: var(--muted); }

.signdate {
  display: block;
  color: var(--muted);
  font-size: 12px;
  font-family: var(--sans);
}

/* ------------------------------------------------------------ footer credit */

.builtby {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
}

.byavatar {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--ground);
  flex: none;
}

/* ------------------------------------------------------- mobile header */

@media (max-width: 600px) {
  header.top { flex-wrap: wrap; row-gap: 6px; padding: 12px 16px; }
  header.top nav { width: 100%; justify-content: flex-start; gap: 16px; }
  header.top nav a { font-size: 13px; white-space: nowrap; }
}

/* ------------------------------------------------------------- x button */

a.btn.xbtn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: #000;
  color: #fff;
}

a.btn.xbtn:hover { background: #1a1a1a; color: #fff; opacity: 1; }

.xglyph { display: block; flex: none; }

/* ------------------------------------------------- verified X metrics */

.vtag {
  display: inline-block;
  font-size: 9px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border: 1px solid var(--accent);
  color: var(--accent);
  border-radius: 999px;
  padding: 0 5px;
  margin-left: 5px;
  vertical-align: 1px;
}

.facts .vtag { font-size: 10px; padding: 1px 6px; }

/* X's own check, next to the handle on a card. */
.xcheck {
  display: inline-block;
  color: var(--accent);
  margin-left: 3px;
  vertical-align: -2px;
}

.vfacts {
  border: 1px solid var(--hairline);
  background: var(--surface);
  border-radius: 8px;
  padding: 10px 14px;
  margin: 0 0 4px;
  font-size: 14px;
  color: var(--muted);
}

/* ------------------------------------------------------------ /book flow */

/* The box no person can see and every form-filling script does. Off-screen
   rather than display:none, because a script that skips hidden fields is
   exactly the script this is here to catch. */
.hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* The radios and the checkboxes on the two optional steps sit in a plain
   list. A fieldset's box would make an optional question look like a form of
   its own, which is the one thing these two screens must not look like. */
.bookopts { border: 0; padding: 0; margin: 24px 0 0; min-width: 0; }

.bookopts legend { padding: 0; margin: 0; font-size: 15px; }

.bookopts label.check {
  margin: 10px 0 0;
  align-items: center;
  color: var(--ink);
}

.bookopts label.check input { margin-top: 0; }

/* The way past a step without answering it, sitting beside the button that
   answers it. Quiet on purpose: skipping is fine, but it is not the thing to
   do first. */
.actions .skip { margin-left: 16px; font-size: 14px; color: var(--muted); }

.actions .skip:hover { color: var(--accent); }

/* ------------------------------------------------------- builder picker */

/* One search box, a dropdown of matches under it and a row of pills for what
   has been picked. The dropdown is positioned, so the fields under it stay
   where they are while the list opens and closes. */
.pick { position: relative; margin: 24px 0 0; }

.pick > label { margin-top: 0; }

.pickresults {
  position: absolute;
  left: 0;
  right: 0;
  z-index: 20;
  max-height: 320px;
  overflow-y: auto;
  margin-top: 4px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px;
}

.pickresults[hidden] { display: none; }

/* One match: the avatar, handle and monospace numbers the directory card
   uses, on one line so eight of them read as a list rather than as cards. */
.pickhit {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-top: 1px solid var(--hairline);
  background: none;
  font-family: inherit;
  font-size: 15px;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.pickhit:first-child { border-top: 0; }

.pickhit:hover, .pickhit:focus { background: var(--row-hover); }

.pickhit img,
.pickhit .avletter { width: 24px; height: 24px; border-radius: 50%; flex: none; }

.pickhitname {
  flex: 1 1 auto;
  min-width: 0;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pickhitnums {
  display: flex;
  gap: 12px;
  flex: none;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
}

@media (max-width: 560px) {
  /* On a phone the numbers cost the handle most of its room, so the follower
     count goes and the price stays: the price is what a pick is decided on. */
  .pickhitnums .flw { display: none; }
}

.pickchips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

.pickchips:empty { margin-top: 0; }

/* Scoped to the picker on purpose. /worth already owns a .chip, further down
   this file, and an unscoped rule here would both lose to it on the cascade
   and hand its own background to every chip on that page. Two classes deep
   beats one wherever the two meet, and reaches nothing else. */
.pickchips .chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 8px 5px 11px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.4;
  color: var(--ink);
}

/* A pill that is not a link does not underline itself when the pointer is on
   its remove button. .chip:hover, written later for /worth, otherwise would. */
.pickchips .chip:hover { text-decoration: none; }

.pickchips .chipx {
  border: 0;
  background: none;
  padding: 0 2px;
  font-family: inherit;
  font-size: 16px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
}

.pickchips .chipx:hover { color: var(--error); }

/* ------------------------------------------------- creators/sponsors seg */

.seg {
  display: inline-flex;
  flex-shrink: 0;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  background: var(--surface);
}

/* 11px of padding, not 7. A segment is a link a finger aims at, on the
   directory bar and on the Listing tabs, and at 7 it was 32 tall against the
   40 every other control on the site is drawn to. The line inside it is 18,
   so 11 either side is the 40. The text does not change: 12px mono is what a
   segment says, and the padding is the only thing between it and the height a
   press is aimed at. The pill round the segments is their own 40 plus the 3px
   it insets them by, which is what keeps the one being read clear of it. */
.seg-item {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--muted);
  text-decoration: none;
  padding: 11px 16px;
  border-radius: 999px;
  transition: none;
}

.seg-item:hover { color: var(--ink); }

.seg-item:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

.seg-active,
.seg-active:hover {
  background: var(--accent);
  color: var(--on-accent);
}

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

a.btn.btn-sm { padding: 8px 14px; font-size: 13px; }

/* ------------------------------------------- admin: two shared rules

   A scroll offset for a heading a link points at, and the muted month under a
   creator cell. Everything else the admin draws is in admin.css. */

body.wide h2[id] { scroll-margin-top: 64px; }

/* The whole creator cell is the link to the deal, so the month under it keeps
   the muted colour instead of taking the link colour. */
td a .cellsub { color: var(--muted); }

/* -------------------------------------- a deal row, the shared half

   Every rule here draws on a page outside /admin as well: .cellval and
   .cellwho on the landing page, .rowact, .waiting and details.more on My
   listing, .formcol on a public form. The admin-only half is in admin.css. */

/* A cell with a second, muted line keeps both lines together. Without the
   wrapper the two would become separate flex items in the phone layout and
   land at opposite ends of the row. */
.cellval { display: block; min-width: 0; }

/* Avatar and the handle under it are one thing, so they stay together when
   the row folds into a card. The avatar's own size is an admin reading and
   is set in admin.css.

   .cellwho stays here: the census draws it on the landing page and on no
   admin page at all. */
.cellwho { display: flex; align-items: center; gap: 8px; min-width: 0; }

.rowact form { display: block; margin-bottom: 4px; }

/* A row action is never the page's primary. The admin's button block in
   app-shell.css paints every button in a cell as an outline. */

/* The step this row is waiting on is not something the owner can do, so it
   reads as a note rather than a button that would be refused. */
.waiting {
  display: block;
  color: var(--muted);
  font-size: 13px;
  white-space: normal;
}

/* Everything that is not the next expected step. Closed by default, so a row
   shows one action. */
details.more { margin-top: 2px; }

details.more summary {
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
}

details.more summary:hover { color: var(--accent); }
details.more form { display: block; margin-top: 4px; }
.rowact details.more a { display: block; margin-top: 4px; font-size: 13px; }

/* A form on an admin page would otherwise stretch the full 1240px column. */
.formcol { max-width: 560px; }

/* A field the form prints rather than asks for. It is not a <label>, because
   it labels no control, so it takes the label's spacing here instead. */
.formcol .lbl { display: block; margin: 18px 0 6px; font-size: 15px; }

/* ------------------------------------------------- tables that stack

   On a phone a table marked .stack is a card of label-and-value lines per
   row, so it never pushes the page sideways. The admin's tables carry the
   class and so do the public directory's, which is why this stays here. */
@media (max-width: 700px) {
  table.stack,
  table.stack tbody,
  table.stack tr,
  table.stack td { display: block; width: 100%; }

  table.stack thead { display: none; }

  table.stack { border-left: 0; border-right: 0; }

  table.stack tr {
    padding: 10px 12px;
    border-bottom: 1px solid var(--hairline);
  }

  table.stack tr:last-child { border-bottom: 0; }

  table.stack td {
    border: 0;
    padding: 2px 0;
    white-space: normal;
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 14px;
  }

  table.stack td::before {
    content: attr(data-label);
    flex: none;
    font-family: var(--mono);
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
  }

  table.stack td .cellval { text-align: right; overflow-wrap: anywhere; }

  /* A wrapping cell has a desktop width that would fight the card layout. */
  table.stack td.wrapcell { min-width: 0; max-width: none; }

  /* Avatar and text keep their pair and move to the value side together. */
  table.stack td .cellwho { justify-content: flex-end; }

  table.stack td.rowact { display: block; margin-top: 8px; }
  table.stack td.rowact::before { display: block; margin-bottom: 4px; }

  /* A finished deal has no next step, so the card does not carry a bare
     "NEXT" label with nothing under it. */
  table.stack td.noact { display: none; }
}

/* ------------------------------------------ availability dots, list view */

/* Six months as six circles: filled is booked, dashed is held by a payment
   that has not landed yet, hollow is open. No colour past the accent, so the
   strip reads as a texture and not as a traffic light. */
.dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 1px solid var(--muted);
  background: transparent;
  flex: none;
}

.dot-booked { background: var(--accent); border-color: var(--accent); }
.dot-held { border-style: dashed; border-color: var(--ink); }
.dot-blocked { background: var(--hairline); border-color: var(--hairline); }

/* The badge slot holds two things at most: the state badge and the booked
   chip. They sit side by side when the head row has room and stack when it
   does not, both hugging the right edge. */
.mcard-badge {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 4px;
  /* The slot shrinks, so two pills on a phone wrap onto two lines instead of
     pushing the card sideways. A single badge is narrower than the space it
     has, so nothing about it moves. */
  flex: 0 1 auto;
  min-width: 0;
}

.badge.booked { color: var(--ink); }

/* Figures are compared down the column, so they line up on the right. */
.dirlist th.num,
.dirlist td.num { text-align: right; }

/* Figures are read down the column and against each other, so the digits are
   fixed-width even where a font would otherwise narrow a 1. */
.dirlist td.num .cellval {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* Under 500 followers the rate is not printed at all, and what stands in for
   it is not a number: it steps back to the heading's colour so the column
   still scans as a column of figures. */
.per1k-dash { color: var(--muted); }
.dirlist .avatar { width: 28px; height: 28px; }

/* The Spots cell is a short sentence with a price in it, not a figure, so it
   sits left. One step down from the figures beside it: it is the answer to a
   second question, and a listing that sells no spot prints a dash that must
   not draw the eye across the row. */
.dirlist td[data-label="Spots"] .cellval {
  font-size: 12px;
  white-space: nowrap;
}

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

/* One pixel and one shade between the two lines of a builder cell. The handle
   is what a sponsor scans the column for, so it is the largest text in the
   row and takes the lighter of the two bold weights; the bio behind it is
   context, so it stays a size down and takes --ink-2 rather than the --muted
   the headings take. */
.dirlist .rowhandle { font-size: 14px; font-weight: 600; }

.dirlist .rowblurb {
  font-size: 13px;
  color: var(--ink-2);
  max-width: none;
  margin-top: 2px;
  white-space: nowrap;
}

/* The column decides the width, so the cell sets none of its own and clips
   instead: that is what puts the ellipsis on the blurb. */
.dirlist td.wrapcell { min-width: 0; max-width: none; overflow: hidden; }
.dirlist a.cellwho:hover .rowhandle { color: var(--accent); }

/* The phone row is a separate cell from the desktop one, so the handle and
   the bio have to be told the same thing twice: the handle is the largest
   text in the row, the bio steps down a shade rather than to --muted. The
   two numbers beside the handle keep their size. */
/* The handle gives ground and the check does not: a row for a fifteen-letter
   account would otherwise put the ellipsis exactly where X's own check is,
   and the one thing this cell is here to say would be the one thing cut. */
.dirlist td.rowline .rlhandle {
  display: flex;
  align-items: center;
  font-size: 14px;
  font-weight: 600;
  flex: 1 1 auto;
  min-width: 0;
}

.dirlist td.rowline .xcheck { flex: none; }

.dirlist td.rowline .rlnum { flex: none; font-size: 12px; }
.dirlist td.rowline .rlfoll { color: var(--muted); }

/* The second line sits under the handle. The face is a cell of its own
   outside this one, so the whole cell already starts where the handle does and
   the line needs no indent. It carries the bio and, when the builder sells
   them, what a spot on the banner costs. The bio is the half that gives
   ground: the price is short, fixed and the thing the second line is widened
   for, so it keeps its width and the bio clips. */
.dirlist td.rowline .rl2 {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 12px;
  color: var(--ink-2);
  min-width: 0;
}

/* Where the ranking stops. Best value ranks on price per 1,000 followers,
   and a listing under the follower floor prints no such number, so it is
   listed after the ranking rather than inside it. One band of ground and the
   hairline under it say so without a word, and the row is hidden from a
   screen reader: the order is the meaning and a reader hears it already. */
.dirlist tr.rankrule td {
  padding: 0;
  height: 8px;
  background: var(--ground);
}

.seg-sm .seg-item { padding: 5px 12px; }

/* The directory is one markup at every width, and this is where it changes
   shape. Seven columns need a screen: the widest of them, 'Per 1k followers',
   sets a floor the 940px container only clears on a laptop, so the table is
   the table down to 1024 and a card per listing below it. That is a wider
   breakpoint than the 700 the rest of the site's tables stack at, and it is
   deliberate: the others stack into label-and-value lines, one screen per
   row, while this one has a one-line cell written for it.

   Everything the site-wide .stack rules do to a table below 700 is undone
   here, because those rules stop at 700 and this shape starts at 1023. The
   selectors carry both classes so they beat them at any width the two
   overlap. */
@media (max-width: 899px) {
  /* A card puts the bio on its own line, so it is free to wrap and the
     table's own column widths are gone. */
  .dirlist .rowblurb { white-space: normal; max-width: none; }
  .dirlist td.wrapcell { min-width: 0; }

  table.stack.dirlist,
  table.stack.dirlist tbody { display: block; width: 100%; }

  table.stack.dirlist thead { display: none; }

  table.stack.dirlist { border-left: 0; border-right: 0; }

  /* The row is the card: the face, then everything else. */
  table.stack.dirlist tr {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 8px 12px;
    border-bottom: 1px solid var(--hairline);
  }

  table.stack.dirlist tr:last-child { border-bottom: 0; }

  /* The directory is the one table a visitor scrolls rather than reads, so it
     does not stack: a label-and-value line per column is a screen each.
     Every column hides and the one-line cell takes the row. The sponsors
     poster is a row of this table too and keeps its own shape. */
  table.stack.dirlist td {
    display: none;
    border: 0;
    padding: 0;
  }

  table.stack.dirlist td::before { content: none; display: none; }

  /* The face is the second column of the table and the first thing on the
     card. order moves it; the markup still draws it once. */
  table.stack.dirlist td.rowav {
    display: block;
    order: -1;
    width: auto;
    flex: none;
  }

  table.stack.dirlist td.rowav .avatar { width: 24px; height: 24px; }

  table.stack.dirlist td.rowline {
    display: block;
    width: auto;
    flex: 1 1 auto;
    min-width: 0;
  }

  table.stack.dirlist tr.rankrule {
    display: block;
    padding: 0;
    border-bottom: 0;
  }

  table.stack.dirlist tr.rankrule td { display: block; width: 100%; padding: 0; }
}

/* ------------------------------------------------------------------ gate

   The two doors into the site: /claim and /sponsors/join. One narrow centered
   column, one card of plain facts, one black X button. Same tokens as the
   landing page and no image of its own. */

.gate {
  max-width: 560px;
  margin: 0 auto;
  text-align: center;
  padding-top: 12px;
}

.gate h1 { font-size: 32px; }

.gate .sub { margin-bottom: 24px; }

.gatecard {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 4px 18px;
  text-align: left;
  margin: 0 0 24px;
}

.gaterow {
  display: flex;
  gap: 16px;
  align-items: baseline;
  padding: 12px 0;
  border-top: 1px solid var(--hairline);
}

.gaterow:first-child { border-top: 0; }

.gatelbl {
  flex: none;
  width: 148px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.gateval { min-width: 0; font-size: 15px; }

.gatebtn { margin: 0 0 14px; }

.gate .rule { margin: 0 0 6px; }


/* The one extra line the claim gate carries under its sub line. It belongs to
   that line rather than to the card below it, so it takes the sub line's size
   and colour instead of the smaller .rule type it sits in. */
.gate .gateintro {
  font-size: 16px;
  color: var(--muted);
  margin-top: 6px;
}

@media (max-width: 560px) {
  .gate h1 { font-size: 26px; }
  .gaterow { display: block; padding: 11px 0; }
  .gatelbl { display: block; width: auto; margin-bottom: 2px; }
}

/* --------------------------------------------------------- sponsor pitch

   /sponsor leads with two small blocks of plain facts before the form, and
   closes with a one-line placeholder box. Same card as the gate pages, a
   hairline box with a mono label and one line per row, but it runs the full
   width of the reading column instead of a centered column of its own. */

.pitch {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 4px 18px;
  margin: 0 0 20px;
}

.pitchlbl {
  margin: 0;
  padding: 12px 0 0;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.pitchline {
  margin: 0;
  padding: 12px 0;
  font-size: 15px;
  border-top: 1px solid var(--hairline);
}

/* The label is not a row of its own, so the first line hangs off it. */
.pitchlbl + .pitchline { border-top: 0; padding-top: 6px; }

/* /sponsor takes the same rows without the box. There the two blocks are the
   page's argument, not two objects sitting on it: as cards they cost a
   border, a fill and 18px of inset either side, and between them they pushed
   the form heading most of a screen down. Same text, same rows, no card. The
   worth and pin pages keep the card, because there the block really is a
   panel of workings standing beside the answer. */
/* The one long headline on the site. It runs five lines on a phone at the
   shared h1 size, so it steps down the way the landing hero does. */
.pitchh1 { margin-bottom: 14px; }

@media (max-width: 480px) {
  .pitchh1 { font-size: 26px; }
}

/* The form heading follows a card, which already carries its own space. With
   the card gone it follows plain lines, so the gap is a heading's gap. */
.pitch + h2 { margin-top: 36px; }

/* --------------------------------------------------------------- pin worth

   /worth is one reading column of blocks it borrows from elsewhere: the
   sponsor pitch card for the index and the method, the listing facts table
   for the estimate, and a plain stacking table for the listings near it.
   Two things are its own. */

/* The button and the quiet link under the facts share one row, and wrap onto
   two on a narrow screen rather than pushing the button off the side. */
.worthact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
  margin: 24px 0 0;
}

.worthact .rule { margin: 0; }

/* The nearby listings sit under a heading of their own, which already carries
   the space above. The space below is the table's own, so the disclosure
   underneath does not sit border to border against it. */
.worthnear { margin-top: 4px; margin-bottom: 24px; }

/* ------------------------------------------------- pin worth: the result

   The result page is the answer and two buttons. Everything that explains the
   answer is folded under it, so the number is the first and largest thing on
   the page and the working is one click away rather than three screens
   down. */

/* The number is the headline, so it runs bigger than a normal h1 and tighter,
   and it is set in the mono face the prices everywhere else use. */
.worthh1 {
  font-family: var(--mono);
  font-size: 34px;
  line-height: 1.18;
  letter-spacing: -0.01em;
  margin: 0 0 14px;
}

/* The range under it: one quiet line, not a table row. */
.worthrange {
  color: var(--muted);
  font-size: 16px;
  margin: 0 0 6px;
}

/* What to do about the number. The one line on the page that is an
   instruction, so it keeps the ink colour the headline has. */
.worthsuggest {
  font-size: 17px;
  font-weight: 700;
  margin: 0 0 4px;
}

/* The share button sits next to the primary one and reads as the quieter of
   the two: same shape, the site's outline instead of its fill. */
a.btn.worthshare {
  background: var(--surface);
  color: var(--accent);
  box-shadow: inset 0 0 0 2px var(--accent);
}

a.btn.worthshare:hover { background: var(--row-hover); opacity: 1; }

/* The proof under the form: what the price is made of, in the quiet type of a
   figure rather than of a sentence. */
.worthproof { font-size: 13px; color: var(--muted); margin: 14px 0 8px; }

/* Three handles to try. Pills, so they read as things to click rather than as
   a list, and they wrap on a narrow screen instead of squeezing. */
.worthex {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.worthex span { font-size: 13px; color: var(--muted); }

.chip {
  display: inline-block;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  padding: 4px 10px;
  border: 1px solid var(--hairline);
  border-radius: 999px;
  color: var(--ink);
  text-decoration: none;
}

.chip:hover { text-decoration: underline; }

/* The live checkbox on /worth sits under the one line of copy and reads as a
   setting rather than as part of the sentence. Shared with /pin. */
.worthlive { margin-top: 14px; }

.worthlive .check { font-size: 14px; }

.worthlive .rule { font-size: 13px; margin: 4px 0 0; }

/* The /worth form alone carries a proof line and examples above the box, so
   there the setting is demoted to the smallest type on the page. /pin has
   nothing between its one line and the box and keeps the sizes above. */
.worthlive-quiet { font-size: 12px; color: var(--muted); }

.worthlive-quiet .check { font-size: 12px; margin: 0; gap: 8px; }

.worthlive-quiet .check input { width: 13px; height: 13px; margin-top: 1px; }

.worthlive-quiet .rule { font-size: 12px; }

@media (max-width: 640px) {
  .worthh1 { font-size: 26px; }
  .worthact .btn { flex: 1 1 auto; text-align: center; }
}

/* ----------------------------------------------------------------- compare

   /compare is the worth page's shapes with one thing of its own: a facts
   table that carries two value columns instead of one. Everything else on
   the page (the headline, the buttons, the share) is already styled above. */

/* Three columns, not a flex row with space between: the two value columns
   have to line up down the whole table or the eye cannot compare them. The
   label column runs a little wider because it holds the longest words. */
.facts.cmp div {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
  align-items: baseline;
  gap: 12px;
}

.facts.cmp span { overflow-wrap: anywhere; }

/* The header row names the two columns and is the one row that is not a
   fact, so it loses the muted label colour and keeps its own weight. */
.facts.cmp .cmphead {
  padding-top: 0;
  align-items: center;
}

.facts.cmp .cmphead span { color: var(--ink); font-weight: 700; }

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

.cmpwho .avatar { flex: 0 0 auto; }

.cmpwho a {
  font-family: var(--mono);
  overflow-wrap: anywhere;
}

/* Two handle boxes and a button. On a wide screen they share the row the
   single-input form uses; the row already stacks under 480px. */
.addrow.cmpform { max-width: 620px; }

@media (max-width: 640px) {
  /* The label column narrows first: 'On the site since' can wrap, a price
     cannot. */
  .facts.cmp div { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1fr) minmax(0, 1fr); gap: 8px; }
  .facts.cmp span { font-size: 14px; }
  /* The tag drops under the price rather than pushing it out of the column,
     and stays on one line: a pill broken over two lines reads as a button. */
  .facts.cmp .vtag { display: table; white-space: nowrap; margin: 3px 0 0; }
  /* Three buttons, so each one takes the width rather than leaving a row of
     one and a row of two. The worth page keeps its two side by side. */
  .worthact.cmpact .btn { flex: 1 1 100%; }
}

/* The pairs the tool page links to. A quiet list under the form, not a second
   feature: the heading is the size of a label and the links stack. */
.cmppoph2 {
  font-size: 15px;
  font-weight: 400;
  color: var(--muted);
  margin: 34px 0 10px;
}

.cmppop {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: 15px;
  columns: 2;
  column-gap: 24px;
}

.cmppop li { padding: 4px 0; break-inside: avoid; }

@media (max-width: 640px) {
  .cmppop { columns: 1; }
}

/* ---------------------------------------------------------- a post's words

   Wherever one is printed: the listing page's timeline and the pin checker's
   block below. The line breaks are the builder's own, so they are kept as
   they were written rather than turned into markup, and a run of blank lines
   was already folded to one break before the text reached the page.

   A post of 280 characters or fewer is shown whole: that is a post that fits.
   A longer one is clamped to six lines, and the browser draws its own ellipsis
   at the end of the last line it shows, which is why the clamp is on the words
   alone. "View on X" sits outside this box, so the way to the rest of the post
   is never the thing that got clamped. */

.xpost-text { white-space: pre-line; }

.xpost-text.xpost-long {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  overflow: hidden;
}

/* A link inside the words of a post. It is the builder's link, not the page's,
   so it carries no arrow and no colour of its own: it reads as the text around
   it and says it is pressable under the pointer. */

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

a.plink:hover, a.plink:focus-visible { text-decoration: underline; }

/* ------------------------------------------------------------- pin checker

   /pin is the worth page's shapes with one thing of its own: the pinned post
   itself, printed under the headline. The listing page already styles a post
   (.xpost-text, .xpost-meta); this is the same post in a block of its own
   rather than in a list, so it borrows those two and adds the frame. */

.pinpost {
  border: 1px solid var(--hairline);
  border-radius: 10px;
  padding: 16px 18px;
  margin: 22px 0 0;
  max-width: 620px;
  background: var(--surface);
}

.pinpost .xpost-text { font-size: 15px; }

.pinpost .xpost-meta { margin-top: 10px; font-size: 12px; }

/* The price line sits under the post rather than under the headline, so it
   needs the space the headline block would otherwise have given it. */
.pinpost + .worthsuggest { margin-top: 18px; }

/* -------------------------------------------------------- pinned post writer

   /write is a plain form and a plain answer. The only new shape is the draft
   itself: a box that can be selected and copied without script, and the same
   words again as text under it. */

.writeform { max-width: 620px; }

/* Read only, so it does not invite typing: no focus accent of its own beyond
   the one every field gets, and the mono face the site uses for anything that
   is quoted rather than written. */
.writedraft {
  max-width: 620px;
  font-size: 15px;
  line-height: 1.6;
  background: var(--surface);
  margin-top: 6px;
}

.writeplain {
  border-top: 1px solid var(--hairline);
  margin: 24px 0 0;
  padding-top: 14px;
  max-width: 620px;
}

.writeplain .xpost-tag { margin-bottom: 8px; }

/* The draft as text, with the blank lines it has. The listing pages collapse
   runs of blank lines when they print somebody else's post, which is right
   for a post but wrong for a draft: the three parts are separated by exactly
   those blank lines, and a copy that loses them is a different post. */
.writeplaintext {
  margin: 0;
  font-size: 15px;
  line-height: 1.6;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  .pinpost { padding: 14px; }
}

/* ------------------------------------------- settings: remove my listing

Last on the Settings page, behind a disclosure the reader has to open. Same
surface and hairline as every other panel on the dashboard: this is a door,
not a warning, and the handle the form asks for is what keeps it from being
opened by accident. The removed state uses the same box without the
disclosure, because there is nothing left to hide. */
.dangerbox {
  border: 1px solid var(--hairline);
  background: var(--surface);
  border-radius: 12px;
  padding: 12px 16px;
  margin-top: 36px;
}

details.dangerbox > summary {
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
  list-style: none;
}

details.dangerbox > summary::-webkit-details-marker { display: none; }

details.dangerbox > summary::before {
  content: "+ ";
  font-weight: 700;
}

details.dangerbox[open] > summary::before { content: "- "; }

details.dangerbox > summary:hover { color: var(--accent); }

.dangerbox .facts { margin-top: 12px; }
.dangerbox .rule { margin: 10px 0 0; }
.dangerbox .liststate { margin: 0; }
.dangerbox .error { margin: 14px 0 0; }
.dangerbox form { margin-top: 4px; }
.dangerbox label { margin-top: 14px; }
.dangerbox input[type="text"] { max-width: 260px; }
.dangerbox .actions { margin-top: 14px; }

/* ---------------------------------------------------------------- .help

   One muted line under a control. The admin and the public pages read this
   one; the dashboard and the builder page have their own inside this file's
   T0 region, which wins by coming later. */

.help {
  display: block;
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
/* T0:start */

/* ================= the dashboard and builder-page system ==================

Prompt T0. Everything the builder's own pages (body.dash) and the public
builder page (body.profile) are drawn with lives between the T0:start and
T0:end marks and nowhere else. scripts/design-lint.js reads exactly this
region: every px in it has to be on the spacing scale and every font-size has
to be one of the seven sizes, so a value that is not part of the system cannot
be added without the check going red.

What it holds, in order: the panel, type, the card, controls, buttons, the
toggle, the pill, the chip, the segmented control, the table and its row menu,
the two grids, notices and empty states, the shared states (hover, focus,
active, disabled, loading), motion, My listing and Settings, the calendar, then
what is particular to each of the two page families. The dashboard's own shell
is in app-shell.css.

Scale. Spacing is 4, 8, 16, 24, 32, 48 and nothing else: label to input 8,
control to helper 8, control to control 16, card padding 24, card to card 24,
section to section 48. Type is three roles: mono for h1, h2, the eyebrow and
the data cells (numbers, handles, dates); sans 15 for body, 13 for labels and
helper lines, 14 for buttons. Two weights, 400 and 500. One control height,
40, and a two-row textarea at 72. One radius for controls, 8; the box that
holds them is 16 (--panel-radius); pills are round. --radius-card is 12 and
two rules still ask for it.

Scoped to body.dash and body.profile, written as that pair of selectors rather
than with :is(), so a rule here has the same weight as the body.dash rules
further up the file and wins by coming after them. The admin pages are drawn
by admin.css and app-shell.css, and nothing in here reaches them. */

/* Sizes that belong to these two pages rather than to the site. --row-h is 48
   here and 44 on the admin, and it is the one token :root sets that this block
   sets again: --card-pad and --gap are only ever defined here. No container
   width and no shell breakpoint is written here at all: app-shell.css draws
   the dashboard's shell and the admin's from one set. */
body.dash,
body.profile {
  --row-h: 48px;
  --card-pad: 24px;
  --gap: 24px;
}

/* ------------------------------------------------------------- the panel

Prompt Q1. The one box on this site. Every container on every page is this
rule: the landing table, the Latest strip, a sponsor card, the profile's
offer box, the inbox, a dashboard card, a tile, the Saved list, the /worth
input block, the Bookings list and the admin tables. Nothing else draws a
border, a radius, a ground and a shadow of its own, so a page that puts its
content in a .panel is right by default and no new page needs a fix.

It is written outside body.dash / body.profile because it is the site's box
and not the dashboard's, and it sits at the top of the T0 region so that
anything more specific after it - the card's padding, the offer box's own
flattening of what is inside it - still wins.

The shadow is two: a one-pixel contact edge so the box sits on the ground,
and a wide, very light lift so a column of boxes reads as a column. Both are
under a tenth of black; the brand is mono and quiet.

The px in the two shadows are not spacing. scripts/design-lint.js takes the
lengths inside a box-shadow out of the scale check for exactly that reason,
and it holds a list of the selectors a box-shadow may be written on at all,
of which these two are the containers. */
.panel {
  background: var(--panel-bg);
  border: var(--panel-border);
  border-radius: var(--panel-radius);
  box-shadow: var(--panel-shadow);
}

/* A panel inside another panel: the mock inside the profile's offer box, the
   calendar's wrapper. The hairline stays, the lift does not - one box does
   not float inside another. */
.panel--flat { box-shadow: none; }

/* Prompt R1. The one dark box on the site: the /worth share card, which is
   the picture og.png draws rendered in HTML. It is the same box as every
   other - same radius, same border width, same lift, all inherited - with
   the ground and the ink swapped for each other. Two tokens, no new hex, and
   nothing here that a design-lint rule is about: a modifier that only names
   colours cannot draw a second kind of container. */
.panel--ink {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--ground);
}

/* A panel that is a link: a sponsor card, a Saved row. The lift grows and the
   box rises a pixel under the pointer. */
.panel--link:hover {
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05), 0 14px 32px -12px rgba(0, 0, 0, 0.16);
  transform: translateY(-1px);
}

/* The head row of a panel: a label over a list, on the box's own hairline. */
.panel > .panel-head {
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}

/* Q2, the one tooltip. A face carries the handle it belongs to, and a reader
   should not have to wait for the browser's own title bubble to read it. The
   wrapper views/tooltips.js draws is `.tip` with the words in data-tip; the
   bubble is drawn here so that the hero, the Latest strip and the profile
   header all get the same one from one rule rather than three copies in
   three area files. It is the only rule this prompt writes in base.css, and
   it is here because it is shared by two area files - written in either of
   them it would have to be written in both.
   Hairline and panel ground, no shadow: it is a label, not a box that
   floats. It shows on focus as well as on hover, so a keyboard reaches it.
   The lift is 2px and the fade is --t-fast, both of which go to zero under
   the reduced-motion block at the top of this file. */
.tip {
  position: relative;
  display: inline-block;
  line-height: 0;
  transition: transform var(--t-fast) ease-out;
}

.tip:hover,
.tip:focus-visible { transform: translateY(-2px); z-index: 2; }

.tip::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%;
  bottom: 100%;
  margin-bottom: 4px;
  transform: translateX(-50%);
  padding: 4px 8px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.4;
  color: var(--ink);
  background: var(--panel-bg);
  border: var(--panel-border);
  border-radius: 8px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-fast) ease-out;
}

.tip:hover::after,
.tip:focus-visible::after { opacity: 1; }

/* The label still shows; the lift is what a reader asking for less movement
   asked not to have. The two time tokens are zero by here already, so this
   is the transform alone. */
@media (prefers-reduced-motion: reduce) {
  .tip { transition: none; }
  .tip:hover,
  .tip:focus-visible { transform: none; }
}


/* Text for a reader who cannot see the layout. The board's rows are laid out
   on a grid, which takes the table meaning off the elements; the roles put it
   back and the caption says what the table is. Neither costs the page a word
   of visible copy. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  overflow: hidden;
  white-space: nowrap;
  clip-path: inset(50%);
}

body.dash,
body.profile { font-size: var(--body); line-height: 1.5; }

/* ------------------------------------------------------------- type */

/* 400, which is the weight these two draw at. Space Mono is two faces, 400
   and 700, so a 500 here would be rendered with the regular and the number
   says which of the two it means. app-shell.css puts the dashboard h1 up to
   700; the public h1 and every h2 keep the regular. */
body.dash h1,
body.profile h1 {
  font-family: var(--mono);
  font-size: var(--h1);
  line-height: 1.25;
  font-weight: 400;
  margin: 0 0 8px;
}

body.dash h2,
body.profile h2 {
  font-family: var(--mono);
  font-size: var(--h2);
  line-height: 1.25;
  font-weight: 400;
  margin: 48px 0 16px;
}

body.dash h3,
body.profile h3 {
  font-family: var(--sans);
  font-size: var(--body);
  font-weight: 500;
  line-height: 1.25;
  margin: 24px 0 8px;
}

/* A card is a section, so its own heading takes the card's padding rather
   than adding a section gap inside it. */
body.dash .card > h2:first-child,
body.profile .card > h2:first-child,
body.dash .dashmain > h2:first-child { margin-top: 0; }

body.dash .dashmain > h1 { margin-top: 0; }

/* One helper line under one control. Sans, 13, muted, and never wider than
   the reading measure even inside a card twice that width. */
body.dash .help,
body.profile .help,
body.dash .rule,
body.profile .rule,
body.dash .sub,
body.profile .sub,
body.dash .pagesub,
body.profile .pagesub {
  display: block;
  font-family: var(--sans);
  font-size: var(--help);
  font-weight: 400;
  line-height: 1.5;
  color: var(--muted);
  max-width: var(--line-max);
  margin: 8px 0 0;
}

/* The status line under an h1: sans, never mono. */
body.dash .facthead,
body.profile .facthead {
  font-family: var(--sans);
  font-size: var(--help);
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 24px;
}

body.dash .facthead a,
body.profile .facthead a { color: var(--accent); }

/* The Listing page's three tabs (prompt Y2). The segmented control the
   directory bar wears, as real links: the strip sits under the line that
   names the listing and the cards follow it. It wraps rather than scrolls, so
   three chips fit a 390 screen with nothing held off the edge.

   The selector names .seg too, so the strip keeps its 24 under it: the
   dashboard's own `.seg { margin: 0 }` is written later in this file at the
   same weight and would otherwise win the tie. */
body.dash .seg.listingtabs {
  display: inline-flex;
  flex-wrap: wrap;
  max-width: 100%;
  margin: 0 0 24px;
}
body.dash .profcard .rowhandle { font-family: var(--mono); font-size: var(--table); }

body.dash p,
body.profile p { line-height: 1.5; }

body.dash b,
body.dash strong,
body.profile b,
body.profile strong { font-weight: 500; }

/* Numbers set in sans line up down a column like the mono ones do. */
body.dash .num,
body.profile .num,
body.dash .moneynum,
body.dash .rowamt,
body.profile .bamt,
body.profile .lamt,
body.profile .spottotal { font-variant-numeric: tabular-nums; }

/* The Log out control, which stays in base.css while the rest of the
   dashboard shell is in app-shell.css. It is a form's button carrying
   .linkish (views/shell.js), and the .linkish rule later in this file writes
   the same colour at the same weight and wins the tie by coming after it. In
   app-shell.css these two would load after .linkish instead and turn the
   button muted. */
body.dash .dashlogout button {
  height: auto;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font: 400 var(--help)/1.5 var(--sans);
  cursor: pointer;
}

body.dash .dashlogout button:hover { color: var(--accent); background: none; border: 0; }

/* ------------------------------------------------------------- the card

The only bordered container on a page. Every section is one, they are all the
same width, and nothing bordered goes inside one except a table, a code box
and the profile mock. */

/* The box itself is `.panel` and every card in the views carries that class
   beside this one, so what is written here is the card's padding, its width
   and the air under it: the layout of a section, not the drawing of a box. */
body.dash .card,
body.profile .card {
  padding: var(--card-pad);
  margin: 0 0 24px;
  max-width: var(--content-max);
}

body.dash .card > :first-child,
body.profile .card > :first-child { margin-top: 0; }

body.dash .card > :last-child,
body.profile .card > :last-child { margin-bottom: 0; }

/* A card's own helper line sits under its heading with the same 8 a control's
   line takes. */
body.dash .card > .help,
body.profile .card > .help { margin: 0 0 16px; }

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

One height for every single-line control, and a two-row textarea at 72. */

body.dash input[type="text"],
body.dash input[type="email"],
body.dash input[type="number"],
body.dash input[type="date"],
body.dash input[type="search"],
body.dash select,
body.profile input[type="text"],
body.profile input[type="email"],
body.profile input[type="number"],
body.profile input[type="date"],
body.profile input[type="search"],
body.profile select {
  height: var(--ctl-h);
  width: 100%;
  padding: 0 12px;
  font-family: var(--sans);
  font-size: var(--body);
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--control-line);
  border-radius: var(--radius);
}

body.dash textarea,
body.profile textarea {
  width: 100%;
  min-height: 72px;
  height: 72px;
  padding: 8px 12px;
  font-family: var(--sans);
  font-size: var(--body);
  line-height: 1.5;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--control-line);
  border-radius: var(--radius);
  resize: vertical;
}

body.dash label,
body.profile label {
  display: block;
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: var(--label);
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
}

body.dash label .hint,
body.profile label .hint {
  display: block;
  font-size: var(--help);
  font-weight: 400;
  color: var(--muted);
  max-width: var(--line-max);
}

body.dash label.check,
body.profile label.check {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  margin: 16px 0 0;
  font-size: var(--help);
  font-weight: 400;
  color: var(--muted);
}

body.dash label.check input,
body.profile label.check input {
  width: auto;
  height: auto;
  margin: 0;
  flex: none;
  accent-color: var(--accent);
}

/* The dollar sign belongs to the field, not to the label: it sits inside the
   same border and the digits start where the sign ends. */
body.dash .pricefield,
body.profile .pricefield {
  display: flex;
  align-items: stretch;
  width: 160px;
  max-width: 100%;
  height: var(--ctl-h);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

body.dash .pricefield .pricecur,
body.profile .pricefield .pricecur {
  display: flex;
  align-items: center;
  padding: 0 12px;
  color: var(--muted);
  border-right: 1px solid var(--hairline);
}

body.dash .pricefield input[type="text"],
body.profile .pricefield input[type="text"] {
  border: 0;
  border-radius: 0;
  background: transparent;
  width: 100%;
  min-width: 0;
}

/* ------------------------------------------------------------ buttons

Two kinds and a third for the two presses that cannot be undone. One primary
per card; everything else is the outline. A button never changes height. */

body.dash .btn,
body.dash button,
body.dash input[type="submit"],
body.profile .btn,
body.profile button,
body.profile input[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: var(--ctl-h);
  min-width: 40px;
  padding: 0 16px;
  margin: 0;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  font: 500 var(--btn)/1 var(--sans);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

body.dash .btn.secondary,
body.dash .btn-quiet,
body.dash button.btn-quiet,
body.dash input[type="submit"].secondary,
body.dash .dangerbox input[type="submit"],
body.profile .btn.secondary,
body.profile .btn-quiet,
body.profile button.btn-quiet,
body.profile input[type="submit"].secondary {
  background: var(--surface);
  border-color: var(--hairline);
  color: var(--ink);
}

/* Red outline, never a red fill. */
body.dash .danger,
body.dash details.dangerbox input[type="submit"] {
  background: transparent;
  border-color: var(--danger);
  color: var(--danger);
}

/* A text link that happens to post a form. It is not a button and must not
   look like one. */
body.dash .linkish,
body.dash button.linkish,
body.profile .linkish,
body.profile button.linkish {
  display: inline;
  height: auto;
  min-width: 0;
  width: auto;
  padding: 0;
  margin: 0;
  border: 0;
  background: none;
  color: var(--accent);
  font: 400 var(--help)/1.5 var(--sans);
  text-decoration: underline;
  cursor: pointer;
}

body.dash .linkcancel { font-size: var(--help); }

body.dash form .actions,
body.profile form .actions { margin: 24px 0 0; }

/* Only the last control of a card carries the button, and it sits at the
   bottom right of it. */
body.dash .card .actions,
body.profile .card .actions { display: flex; justify-content: flex-end; }

/* The one exception, and it exists only for a reader with no script: the
   submit button a switch keeps inside <noscript> belongs to the switch above
   it, so it stays on the left edge under it rather than being pulled to the
   card's right edge with the Save buttons. In every browser that runs this
   file's sibling under public/, a <noscript> has no children to lay out. */
body.dash noscript .actions,
body.profile noscript .actions { display: block; margin: 16px 0 0; }

body.dash form,
body.profile form { margin: 0; }

/* A second form in the same block is a second setting, and it takes the air
   two cards take. Without this the helper line under one setting butts
   against the label of the next with nothing between them, which is what the
   Pin and Banner tabs would read like: they stack three forms in one card.
   The first form in a card is still flush with the heading. */
body.dash .card > * + form,
body.dash .help + form { margin-top: 24px; }

/* The sponsor card is the one long stack of fields on the dashboard: label,
   control, label, control, six times over, with nothing between one field and
   the next. 16 between them, the same 16 that separates two settings
   everywhere else. Scoped to this form, because a label that follows a
   control anywhere else on a dashboard page is a switch or a segment and must
   stay where it is. */
body.dash .sponsorform input + label,
body.dash .sponsorform select + label,
body.dash .sponsorform textarea + label { margin-top: 16px; }

/* ------------------------------------------------------------- toggle

A real checkbox with role="switch", 36 by 20, its label to the right and one
helper line under the pair. The label names the setting, never the state: the
switch is what says on or off. */

body.dash .toggle,
body.profile .toggle {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  min-height: 40px;
  font-family: var(--sans);
  font-size: var(--body);
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}

body.dash .toggle input[type="checkbox"],
body.profile .toggle input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  position: relative;
  width: 36px;
  height: 20px;
  margin: 0;
  padding: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-pill);
  background: var(--hairline);
  cursor: pointer;
  transition: background var(--t-fast) linear;
}

body.dash .toggle input[type="checkbox"]::after,
body.profile .toggle input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 1px;
  left: 1px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
  transition: transform var(--t-fast) ease;
}

body.dash .toggle input[type="checkbox"]:checked,
body.profile .toggle input[type="checkbox"]:checked {
  background: var(--accent);
  border-color: var(--accent);
}

body.dash .toggle input[type="checkbox"]:checked::after,
body.profile .toggle input[type="checkbox"]:checked::after { transform: translateX(16px); }

body.dash .toggle input[type="checkbox"]:disabled,
body.profile .toggle input[type="checkbox"]:disabled { opacity: 0.5; cursor: default; }

/* The knob is the checkbox's ::after, so the span the markup still carries
   is in the page only for a browser that has neither. */
body.dash .toggle .knob,
body.profile .toggle .knob { display: none; }

/* Without a script the switch is a checkbox that has to be submitted, so the
   form carries a button that only a no-script reader ever sees. The gap above
   it is the 16 that noscript .actions already sets and nothing more: a margin
   of its own here stacked with that one and put the button 24 below the
   switch, which is not the gap either rule was written for. */

/* --------------------------------------------------------------- pill

One shape for every status. Grey ground, black text, a coloured dot on the
left, and a minimum width so a column of them stays straight. Status is never
colour alone: the dot and the word always travel together. */

body.dash .pill,
body.profile .pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-width: 88px;
  min-height: 20px;
  padding: 4px 8px;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.25;
  white-space: nowrap;
}

body.dash .pill .dot,
body.profile .pill .dot {
  display: inline-block;
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--muted);
}

body.dash .dot-paid,
body.profile .dot-paid,
body.dash .dot-posting { background: var(--accent); }

body.dash .dot-pinned,
body.profile .dot-pinned,
body.dash .dot-listed,
body.dash .dot-live,
body.dash .dot-approved { background: var(--saved); }

body.dash .dot-refunded,
body.profile .dot-refunded,
body.dash .dot-rejected { background: var(--danger); }

/* The board's one badge: the sponsor who is drawn largest on the banner. */
body.profile .dot-headline { background: var(--accent); }


/* --------------------------------------------------------------- chip

Only where a reader picks one of a set. 32 tall, radius 8, the same width
across a row, and the state is the style rather than a word inside it. */

body.dash .chips,
body.profile .chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

body.dash .chips legend,
body.profile .chips legend {
  padding: 0;
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: var(--label);
  font-weight: 500;
  color: var(--ink);
}

body.dash .chips form,
body.profile .chips form { display: inline-flex; margin: 0; padding: 0; }

body.dash .chips .chip,
body.profile .chips .chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 32px;
  min-width: 56px;
  width: auto;
  margin: 0;
  padding: 0 12px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 400;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
}

/* Same width within a row, which is the rule the chip primitive carries: the
   chips share the row equally, up to 160 each, so "Sep" and "1st-15th" are the
   same box rather than two boxes the length of their words. A chip that is its
   own form (the availability rows, which have no script) is the flex item and
   the chip inside it fills it.

   112 is the floor rather than 56: below that the longest label a slot chip
   carries ("16th-end booked") is wider than the box, and a row that cannot
   give every chip 112 wraps instead of cutting a word in half. Equal within
   the row it is on, which is what the rule asks. */
body.dash .chips form,
body.profile .chips form,
body.dash .chips > .chip,
body.profile .chips > .chip { flex: 1 1 0; min-width: 112px; max-width: 160px; }

body.dash .chips form .chip,
body.profile .chips form .chip { width: 100%; min-width: 0; max-width: none; }

/* The radio inside a chip is the control; it is never drawn, and it stays in
   the page so it is still focusable and still submitted. */
body.dash .chips .chip input,
body.profile .chips .chip input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  opacity: 0;
  pointer-events: none;
}

body.dash .chips .chip-on,
body.profile .chips .chip-on,
body.dash .chips .chip:has(input:checked),
body.profile .chips .chip:has(input:checked) {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

body.dash .chips .chip-blocked,
body.profile .chips .chip-blocked,
body.dash .chips .chip-booked,
body.profile .chips .chip-booked {
  background: var(--ground);
  border-color: var(--hairline);
  color: var(--muted);
}

body.dash .chips .chip-progress,
body.profile .chips .chip-progress,
body.dash .chips .chip-held,
body.profile .chips .chip-held {
  background: var(--surface);
  border-style: dashed;
  border-color: var(--muted);
  color: var(--ink);
}

body.dash .chips .chip:has(input:disabled),
body.profile .chips .chip:has(input:disabled),
body.dash .chips .chip[disabled],
body.profile .chips .chip[disabled],
body.dash .chips .chip[aria-disabled="true"],
body.profile .chips .chip[aria-disabled="true"] { cursor: default; }

/* --------------------------------------------------- segmented control

Two or three options that exclude each other. Radios inside, never drawn, so
the arrow keys work the way they do in any radio group. */

body.dash .seg,
body.profile .seg {
  display: inline-flex;
  padding: 0;
  margin: 0;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

body.dash .seg .segradio,
body.profile .seg .segradio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
  pointer-events: none;
}

body.dash .seg .segopt,
body.profile .seg .segopt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  min-width: 88px;
  padding: 0 16px;
  margin: 0;
  border-left: 1px solid var(--hairline);
  font-family: var(--sans);
  font-size: var(--btn);
  font-weight: 400;
  color: var(--ink);
  cursor: pointer;
}

body.dash .seg .segopt:first-of-type,
body.profile .seg .segopt:first-of-type { border-left: 0; }

body.dash .seg .segradio:checked + .segopt,
body.profile .seg .segradio:checked + .segopt {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  font-weight: 500;
}

body.dash .seg .segradio:focus-visible + .segopt,
body.profile .seg .segradio:focus-visible + .segopt {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The banner style picker (Settings). The same segmented control the rest of
   the dashboard uses, with a drawing of each layout over its name instead of
   the name alone: three words cannot say where the sponsors sit and three
   pictures can. The thumbnails are flat SVG files in public/ and they shrink
   with the row, so the three of them fit a phone without wrapping the
   control. */
body.dash .seg.bstyleseg { display: flex; width: 100%; max-width: 560px; }

body.dash .seg.bstyleseg .segopt {
  flex: 1 1 0;
  min-width: 0;
  height: auto;
  flex-direction: column;
  gap: 4px;
  padding: 8px;
}

/* ------------------------------------------------------------- tables

One table. A muted uppercase header, 48px rows, one hairline between them, no
zebra. Text left, numbers right in mono, and a handle is never cut in half. */

body.dash table,
body.profile table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border: 0;
  font-family: var(--sans);
  font-size: var(--body);
}

body.dash thead th,
body.profile thead th,
body.dash table > tr > th,
body.profile table > tr > th {
  height: 32px;
  padding: 0 12px;
  font: 500 12px/1.25 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
  text-align: left;
  white-space: nowrap;
  vertical-align: middle;
  border-bottom: 1px solid var(--hairline);
}

body.dash td,
body.profile td {
  height: var(--row-h);
  padding: 0 12px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
  white-space: nowrap;
}

body.dash tr:last-child td,
body.profile tr:last-child td { border-bottom: 0; }

body.dash td.mono,
body.profile td.mono,
body.dash td.num,
body.profile td.num { font-family: var(--mono); font-size: var(--table); }

body.dash td.num,
body.profile td.num,
body.dash th.num,
body.profile th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* Builder-supplied text is capped and cut with an ellipsis; the whole of it
   is in the cell's title, which is what a hover shows. */
body.dash td.wrapcell,
body.profile td.wrapcell {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* position: relative so the wrapper is the containing block for anything
   inside it that is positioned: an .sr-only span is absolute, and against a
   static wrapper it is laid out on the page instead of in the scroller, which
   widens the whole document by however far the table reaches. */
body.dash .table-wrap,
body.profile .table-wrap {
  position: relative;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* A table never reflows into cards: below 700 it scrolls sideways inside its
   own box with the first column held on the left edge. */
@media (max-width: 700px) {
  body.dash .table-wrap td:first-child,
  body.profile .table-wrap td:first-child,
  body.dash .table-wrap th:first-child,
  body.profile .table-wrap th:first-child {
    position: sticky;
    left: 0;
    background: var(--surface);
  }
}

/* ---------------------------------------------------------- row menu

Everything a row can do that is not its next step, behind one mark at the
right end of it.

The prompt says both "actions in one ... menu 32px wide" and "every clickable
thing is at least 40x40", and calls the second non-negotiable. The mark is
40x40 and the cell is 40 wide, so the cell holds what is in it. */

body.dash td.rowact,
body.profile td.rowact { position: relative; text-align: right; width: 40px; }

body.dash td.rowact form,
body.profile td.rowact form { display: inline-block; margin: 0; vertical-align: middle; }

body.dash td.rowact details.more,
body.profile td.rowact details.more {
  display: inline-block;
  margin: 0;
  vertical-align: middle;
  position: relative;
}

body.dash td.rowact details.more > summary,
body.profile td.rowact details.more > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius);
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}

body.dash td.rowact details.more > summary::-webkit-details-marker,
body.profile td.rowact details.more > summary::-webkit-details-marker { display: none; }

body.dash td.rowact .menucard,
body.profile td.rowact .menucard {
  position: absolute;
  right: 0;
  top: 40px;
  z-index: 20;
  min-width: 160px;
  max-width: var(--line-max);
  padding: 4px;
  text-align: left;
  white-space: normal;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}

body.dash td.rowact .menucard form,
body.profile td.rowact .menucard form { display: block; margin: 0; }

body.dash td.rowact .menucard button,
body.dash td.rowact .menucard a,
body.profile td.rowact .menucard button,
body.profile td.rowact .menucard a {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  width: 100%;
  height: 40px;
  padding: 0 8px;
  margin: 0;
  border: 0;
  border-radius: var(--radius);
  background: none;
  color: var(--ink);
  font: 400 var(--btn)/1 var(--sans);
  text-align: left;
  text-decoration: none;
}

@media (min-width: 700px) {
  body.dash .table-wrap:has(td.rowact details.more[open]),
  body.profile .table-wrap:has(td.rowact details.more[open]) { overflow: visible; }
}

@media (max-width: 699px) {
  body.dash td.rowact .menucard,
  body.profile td.rowact .menucard {
    position: static;
    min-width: 0;
    max-width: none;
    margin-top: 4px;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }
}

/* -------------------------------------------------------------- grids

Two equal columns with a 24 gutter from 1024 up, one below it. A control sits
on the grid; nothing is left-aligned with the rest of its row empty. */

body.dash .fgrid,
body.profile .fgrid,
body.dash .grid2,
body.profile .grid2 {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px 24px;
  align-items: start;
  margin: 0;
}

@media (min-width: 1024px) {
  body.dash .fgrid,
  body.profile .fgrid,
  body.dash .grid2,
  body.profile .grid2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

body.dash .fgrid > *,
body.profile .fgrid > *,
body.dash .grid2 > *,
body.profile .grid2 > * { min-width: 0; }

body.dash .fgrid .span2,
body.profile .fgrid .span2,
body.dash .grid2 .span2,
body.profile .grid2 .span2 { grid-column: 1 / -1; }

/* A button in the grid is a cell like any other, and it drops by the height
   of a label so it sits on the same line as the inputs beside it. */
body.dash .fgrid .span2 input[type="submit"],
body.profile .fgrid .span2 input[type="submit"],
body.dash .grid2 .span2 input[type="submit"],
body.profile .grid2 .span2 input[type="submit"] { float: right; }

/* --------------------------------------------------- notices and empties

A notice is one 13px line with a coloured dot, placed where the thing
happened. No banners, no boxes, no toasts. */

/* Block, not flex: a notice can carry a link in the middle of its sentence,
   and flex would drop the space either side of it. The dot is placed instead
   of laid out, so the text keeps its own line box. */
body.dash .note,
body.profile .note,
body.dash .saved,
body.dash .spotpaid,
body.profile .spotpaid,
body.dash .error,
body.profile .error {
  display: block;
  position: relative;
  margin: 8px 0 0;
  padding: 0 0 0 16px;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: var(--help);
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
  max-width: var(--line-max);
}

body.dash .note::before,
body.profile .note::before,
body.dash .saved::before,
body.dash .spotpaid::before,
body.profile .spotpaid::before,
body.dash .error::before,
body.profile .error::before {
  content: "";
  position: absolute;
  left: 0;
  top: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--muted);
  padding: 0;
}

/* A switch carries its own line inside its own form, so two switches in one
   card never write into each other's. Until it has something to say it takes
   no room and no dot: an empty notice is not a notice. */
body.dash .js-toggle-note:empty,
body.profile .js-toggle-note:empty { display: none; }

body.dash .note-ok::before,
body.profile .note-ok::before,
body.dash .saved::before,
body.dash .spotpaid::before,
body.profile .spotpaid::before { background: var(--saved); }

body.dash .note-warn::before,
body.profile .note-warn::before { background: #B4690E; }

body.dash .note-err,
body.profile .note-err,
body.dash .error,
body.profile .error { color: var(--error); }

body.dash .note-err::before,
body.profile .note-err::before,
body.dash .error::before,
body.profile .error::before { background: var(--error); }

/* The field the red line is about. */
body.dash .is-error,
body.profile .is-error,
body.dash .is-error input[type="text"],
body.profile .is-error input[type="text"] { border-color: var(--error); }

/* One muted line, the height of a row, wherever a list or a table is empty.
   Block rather than flex for the same reason the notice above is: an empty
   state often carries the link out of it. */
body.dash .empty,
body.profile .empty {
  display: block;
  min-height: var(--row-h);
  margin: 0;
  padding: 12px 0;
  border: 0;
  background: none;
  font-family: var(--sans);
  font-size: var(--body);
  line-height: 1.5;
  color: var(--muted);
}

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

Hover, focus, active and disabled, defined once and reused by everything
above. */

body.dash .btn:hover,
body.dash button:hover,
body.dash input[type="submit"]:hover,
body.profile .btn:hover,
body.profile button:hover,
body.profile input[type="submit"]:hover {
  background: color-mix(in srgb, var(--accent) 92%, #000000);
  border-color: color-mix(in srgb, var(--accent) 92%, #000000);
  opacity: 1;
}

body.dash .btn.secondary:hover,
body.dash .btn-quiet:hover,
body.dash button.btn-quiet:hover,
body.dash .dangerbox input[type="submit"]:hover,
body.profile .btn.secondary:hover,
body.profile .btn-quiet:hover,
body.profile button.btn-quiet:hover {
  background: color-mix(in srgb, var(--surface) 96%, #000000);
  border-color: var(--hairline);
  color: var(--ink);
}

body.dash .danger:hover,
body.dash details.dangerbox input[type="submit"]:hover {
  background: color-mix(in srgb, var(--surface) 96%, #000000);
  border-color: var(--danger);
  color: var(--danger);
}

body.dash .linkish:hover,
body.profile .linkish:hover { background: none; border: 0; color: var(--accent); }

body.dash .chips .chip:hover,
body.profile .chips .chip:hover { background: color-mix(in srgb, var(--surface) 96%, #000000); }

body.dash tbody tr:hover,
body.profile tbody tr:hover { background: color-mix(in srgb, var(--surface) 96%, #000000); }

body.dash td.rowact details.more > summary:hover,
body.profile td.rowact details.more > summary:hover { background: var(--row-hover); color: var(--ink); }

body.dash td.rowact .menucard button:hover,
body.dash td.rowact .menucard a:hover,
body.profile td.rowact .menucard button:hover,
body.profile td.rowact .menucard a:hover { background: var(--row-hover); }

/* One ring, on everything a keyboard can reach. */
body.dash a:focus-visible,
body.dash button:focus-visible,
body.dash input:focus-visible,
body.dash select:focus-visible,
body.dash textarea:focus-visible,
body.dash summary:focus-visible,
body.dash label.toggle:focus-within,
body.profile a:focus-visible,
body.profile button:focus-visible,
body.profile input:focus-visible,
body.profile select:focus-visible,
body.profile textarea:focus-visible,
body.profile summary:focus-visible,
body.profile label.toggle:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

body.dash input:focus,
body.dash textarea:focus,
body.dash select:focus,
body.profile input:focus,
body.profile textarea:focus,
body.profile select:focus { border-color: var(--accent); }

body.dash .btn:active,
body.dash button:active,
body.dash input[type="submit"]:active,
body.profile .btn:active,
body.profile button:active,
body.profile input[type="submit"]:active { box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); }

body.dash [disabled],
body.profile [disabled],
body.dash [aria-disabled="true"],
body.profile [aria-disabled="true"] { opacity: 0.5; cursor: default; }

/* A block that is on the page but cannot be used until something above it
   has happened. Dimmed, never inert: the server is what refuses the work. */
body.dash .greyed { opacity: 0.5; }

/* ------------------------------------------------------------- motion

150ms on hover and on the toggle, 200 on the preview swap, 300 on a row
arriving on the live board. Everything is off under prefers-reduced-motion,
which the tokens at the top of the file do. */

body.dash .btn,
body.dash button,
body.dash .chip,
body.dash tbody tr,
body.profile .btn,
body.profile button,
body.profile .chip,
body.profile tbody tr { transition: background var(--t-fast) linear, border-color var(--t-fast) linear, color var(--t-fast) linear; }

/* A row that opens is a disclosure with no marker and a pointer, and that is
   all this rule says: how the cells sit is .rowgrid, which every row wears,
   the summary of one that opens and the anchor of one that is a link to its
   thread. Nothing here lays a summary out itself: a flex row on this selector
   beats .rowgrid on specificity and puts the amounts on five different x. */
body.dash .dealrow > summary {
  cursor: pointer;
  list-style: none;
}

body.dash .dealrow > summary::-webkit-details-marker { display: none; }

body.dash .dealrow .rowmain { display: flex; align-items: center; gap: 8px; min-width: 0; }
body.dash .dealrow .rowname { display: flex; flex-direction: column; min-width: 0; }
body.dash .dealrow .rowhandle { font-family: var(--mono); font-size: var(--table); }


body.dash details.pastrows > summary,
body.dash .boardmore > summary {
  display: inline;
  list-style: none;
  font-size: var(--help);
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
}

@media (max-width: 560px) {
  body.dash .dealrow > summary { flex-wrap: wrap; }
}

/* My listing and Settings. */
body.dash .liststate { font-size: var(--help); color: var(--muted); margin: 8px 0 0; }

body.dash .formhead { font-family: var(--sans); font-size: var(--body); font-weight: 500; margin: 24px 0 8px; }

body.dash .facts { border-top: 1px solid var(--hairline); margin: 0; }

body.dash .facts div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  min-height: 40px;
  align-items: center;
  border-bottom: 1px solid var(--hairline);
  padding: 0;
  font-size: var(--body);
}

body.dash .facts span:first-child { color: var(--muted); }

/* A fact's value is a Stripe account id or an address: at 390 it is longer
   than the column, and a flex item does not shrink below its content unless
   it is told it may. It wraps inside the card rather than over its edge.

   Only the value breaks mid-word. Named on both spans it would break the
   name of the fact as well, and at 390 "Status" comes out as "Stat us". */
body.dash .facts span { min-width: 0; }

body.dash .facts span:last-child { text-align: right; overflow-wrap: anywhere; }

/* Taking the listing off the board. A red outline button and nothing else:
   the box has no ground of its own. */
body.dash .dangerbox {
  background: none;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-card);
  padding: var(--card-pad);
  margin: 0 0 24px;
  max-width: var(--content-max);
}

body.dash .dangerbox > summary {
  list-style: none;
  cursor: pointer;
  font-family: var(--mono);
  font-size: var(--h2);
  color: var(--ink);
}

body.dash .dangerbox > summary::-webkit-details-marker { display: none; }
body.dash .dangerbox > summary::marker { content: ""; }
body.dash .cancelform { display: none; }
body.dash .connectbtn { margin: 16px 0 0; }
body.dash .connline form { display: inline; margin: 0; }

/* Three lines belong to the input rather than to the row, so all three run
   under the input and the gap beside it and stop before the press: what the
   day comes to, what accounts this size charge, and the helper. */
body.dash .priceline > .pricederived {
  grid-column: 1 / 3;
  grid-row: 3;
}

body.dash .priceline > .pricehint {
  grid-column: 1 / 3;
  grid-row: 4;
}

body.dash .priceline > .help {
  grid-column: 1 / 3;
  grid-row: 5;
  margin: 0;
  max-width: var(--line-max);
}

/* On a phone the row has no width to spare: the gap column collapses to
   nothing and a helper squeezed into what is left runs over four lines. All
   three take the whole row there, which is the width the input has too. */
@media (max-width: 700px) {
  body.dash .priceline > .pricederived,
  body.dash .priceline > .pricehint,
  body.dash .priceline > .help { grid-column: 1 / -1; }
}

/* -------------------------------------------------------- the calendar

One component, on the sponsor's booking form and on the builder's Pin tab
(prompt W). Two months side by side, each a table of 40px days: the fill is
the state and the legend under the grid is where the fills are read, because
a state is never colour alone.

Both body classes share it, so every selector here is written from the .cal
wrapper rather than from the class alone. A button on this site is 40 tall,
bordered and blue by the page's own rule, and a table cell is 48 tall with a
hairline under it; both of those are more specific than a bare class, so a
grid written as .calday alone comes out as ninety blue buttons in a ruled
table. The wrapper is what holds them off.

Nothing here hides a field. With no script the grid is a picture of what is
free and the plain date fields under it are the control, which is why the two
month presses stay hidden and the days keep the ordinary cursor until cal.js
adds is-live. */

.cal { margin: 0 0 16px; }

.cal .calnav {
  display: flex;
  gap: 8px;
  margin: 0 0 8px;
}

.cal .calstep {
  height: var(--ctl-h);
  padding: 0 16px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  background: var(--surface);
  font: 500 var(--btn)/1 var(--sans);
  color: var(--ink);
  cursor: pointer;
}

.cal .calstep:disabled {
  color: var(--muted);
  cursor: default;
}

.cal .calmonths {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

/* Two months side by side where there is room for two, one under the other
   where there is not. No breakpoint decides it: a month is seven 40px days
   wide and will not go narrower, so the row wraps itself the moment the column
   it sits in cannot hold two. That is what puts two months on the builder's
   card and one in the sponsor's narrower buy column. */
.cal .calmonth {
  flex: 1 1 auto;
  width: auto;
  border-collapse: collapse;
  background: none;
}

.cal .calmonth caption {
  margin: 0 0 8px;
  font-size: var(--label);
  font-weight: 500;
  color: var(--ink);
  text-align: left;
}

.cal .calmonth th {
  height: auto;
  padding: 0 0 4px;
  font: 400 var(--eyebrow)/1.25 var(--sans);
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
  text-align: center;
  border-bottom: 0;
}

/* One pixel of padding on every cell is the whole grid gap: a day is a chip
   of its own, and two booked days beside each other read as two days rather
   than as one block with a notch in it. */
.cal .calmonth td {
  height: auto;
  padding: 1px;
  border-bottom: 0;
}

.cal .calday {
  display: block;
  width: 100%;
  min-width: 40px;
  height: var(--ctl-h);
  margin: 0;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  font: 400 var(--label)/1 var(--mono);
  color: var(--ink);
  text-align: center;
  cursor: default;
}

.cal.is-live .calday[data-pick] { cursor: pointer; }
.cal.is-live .calday[data-pick]:hover { background: var(--row-hover); }

.cal .calday:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The five states. Open is the hairline outline every quiet border on the
   site uses; closed is the same grey a closed slot takes everywhere; booked
   is the one accent this system has; a day outside the window carries no border at
   all and is read as absent rather than as closed. */
.cal .calday.cal-open { border-color: var(--hairline); }

.cal .calday.cal-blocked {
  border-color: var(--hairline);
  background: var(--ground);
  color: var(--muted);
}

.cal .calday.cal-taken {
  border-color: var(--accent);
  background: var(--accent);
  color: var(--on-accent);
}

/* Asked for, not paid for (prompt Y2): the accent as an outline over a hatch
   rather than the solid fill a booking wears, because nothing holds the day.
   Read-only, so it never carries the hover a pressable day has. */
.cal .calday.cal-requested {
  border-color: var(--accent);
  background: repeating-linear-gradient(135deg, var(--row-new) 0, var(--row-new) 4px, var(--surface) 4px, var(--surface) 8px);
  color: var(--ink);
}

.cal .calday.cal-past { color: var(--hairline); }

/* The picked run: the accent as an outline and its own pale fill, so it reads
   against an open day without borrowing the fill a booked day has. */
.cal .calday.is-sel {
  border-color: var(--accent);
  background: var(--row-new);
  color: var(--ink);
}

.cal .callegend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 16px 0 8px;
  font-size: var(--label);
  color: var(--muted);
}

.cal .calkey {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.cal .calmark {
  width: 16px;
  height: 12px;
  border: 1px solid var(--hairline);
  border-radius: 4px;
  background: var(--surface);
}

.cal .calmark.cal-blocked { background: var(--ground); }
.cal .calmark.cal-requested {
  border-color: var(--accent);
  background: repeating-linear-gradient(135deg, var(--row-new) 0, var(--row-new) 4px, var(--surface) 4px, var(--surface) 8px);
}
.cal .calmark.cal-taken { border-color: var(--accent); background: var(--accent); }

.cal .calnote {
  margin: 0 0 8px;
  font-size: var(--label);
  color: var(--muted);
}

.cal .calnote.is-err { color: var(--error); }

/* The address that is waiting. The box above holds what is published, so this
   is the only place a first submission appears at all; it is cut at the width
   of the column and the whole of it is on the title. */
body.dash .linkaddr,
body.dash .linkrefused {
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--label);
  color: var(--muted);
}

body.dash .linkrefused { margin: 4px 0 0; max-width: 100%; }

body.dash .statlink { margin: 16px 0 0; font-size: var(--help); }

body.dash tr.spotrowback td { color: var(--muted); }
body.dash tr.spotrowoff td { color: var(--muted); }
/* The handle in those rows is a link, and a link carries its own colour, so
   muting the cell left the one word in the row still at full ink. A refunded
   or hidden row is muted whole or it is not muted. */
body.dash tr.spotrowback .bhandle,
body.dash tr.spotrowoff .bhandle { color: var(--muted); }
body.dash tr.spotrowoff img.avatar,
body.dash tr.spotrowoff .avatar.avletter { opacity: 0.5; }

/* Promote: three panels behind three labels, and the pictures in them. The
   Banner page's Pricing control shares the panel machinery below and wears the
   segmented control instead of the tab bar: two options that exclude each
   other are a bar, three panels of pictures are tabs. */
body.dash .tabs > .tabradio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
  pointer-events: none;
}

body.dash .tabs > .tab-badge:checked ~ .panel-badge,
body.dash .tabs > .tab-card:checked ~ .panel-card,
body.dash .tabs > .tab-ring:checked ~ .panel-ring { display: block; }

body.dash .tabs > .tab-badge:checked ~ .tabbar .pick-badge,
body.dash .tabs > .tab-card:checked ~ .tabbar .pick-card,
body.dash .tabs > .tab-ring:checked ~ .tabbar .pick-ring {
  color: var(--ink);
  font-weight: 500;
  border-bottom-color: var(--accent);
}

body.dash .tabs > .tab-badge:focus-visible ~ .tabbar .pick-badge,
body.dash .tabs > .tab-card:focus-visible ~ .tabbar .pick-card,
body.dash .tabs > .tab-ring:focus-visible ~ .tabbar .pick-ring {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* ================================================ the public builder page */

body.profile main { max-width: var(--profile-max); }
body.profile header.top,
body.profile footer { max-width: var(--profile-max); }

/* The header: who the account is, and who has sponsored it. */
body.profile .phead { display: flex; align-items: flex-start; gap: 16px; }

/* The largest of the four avatar sizes. The site-wide .avatar.big is 64,
   which is not one of them, so the builder page draws its own. */
body.profile .phead .avatar.big { width: 56px; height: 56px; border-radius: 50%; }
body.profile .pheadmain { flex: 1 1 auto; min-width: 0; }
body.profile .pheadmain h1 { margin: 0; }
body.profile .phandle { margin: 4px 0 0; font-family: var(--mono); font-size: var(--help); color: var(--muted); }
body.profile .pfacts { margin: 8px 0 0; font-size: var(--help); color: var(--muted); }
/* The numbers, on their own line under the bio (paint7): in the ink and at
   the heavier weight, with only the dots between them muted. */
body.profile .pnums { margin: 4px 0 0; font-size: var(--help); color: var(--ink); font-weight: 600; }
body.profile .phandle + .pnums,
body.profile .pheadmain h1 + .pnums { margin-top: 8px; }
body.profile .pnums .psep { color: var(--muted); font-weight: 400; }
body.profile .psponsors { flex: none; text-align: right; }
body.profile .pfaces { display: block; }

/* The face stack: the smallest of the four avatar sizes, each overlapping the
   one before it by 8, with the card's own ground as the gap between them.
   Without this the site-wide .avatar (36) draws them and the stack is a row
   of overlapping heads as wide as the header. */
body.profile .pfaces .avatar.tiny {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--surface);
  vertical-align: middle;
}

body.profile .pfaces .avatar.tiny + .avatar.tiny { margin-left: -8px; }

/* The letter that stands in for a face cannot be overlapped.

   A photo can: eight pixels off its right edge is eight pixels of somebody's
   shoulder, which is the whole point of a stack. A letter is drawn in the
   middle of its own circle and is about seven pixels wide inside twenty, so
   the next circle sitting eight pixels over it covers the last of the glyph
   and the row reads as one clipped mark rather than as an S beside an S.

   So the overlap is given up, and only where a letter is really showing: the
   twin whose picture failed, and the face that comes after such a twin. A
   stack whose pictures all arrive is laid out without it. Two pixels rather
   than none, so two circles of the same grey are still two circles. */
body.profile .pfaces .avatar.tiny + img[data-letter][data-fail] + .avletter,
body.profile .pfaces img[data-letter][data-fail] + .avletter + .avatar.tiny {
  margin-left: 2px;
}

/* The first twin has nothing to overlap. It stands in for a picture that is
   the first child, and that picture carries no margin, so neither may it: the
   rule above keys on two faces in a row and the twin borrows the picture's
   classes, which is enough to match it against its own hidden partner. Left
   alone the whole stack hangs eight pixels outside the line box it is laid
   out in. */
body.profile .pfaces img.avatar.tiny:first-child + .avletter { margin-left: 0; }

body.profile .pfacecount { display: block; margin-top: 8px; font-size: 12px; color: var(--muted); }

/* What the builder has actually sold, under the header (prompt Y4). The
   avatars above say how many sponsors there have been; these say what they
   bought. One line each, wrapping, quiet: it is evidence, not the offer. */
body.profile .pbookedh {
  margin: 16px 0 4px;
  font-size: var(--help);
  line-height: 1.5;
  color: var(--muted);
}
body.profile .pbooked {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  list-style: none;
  margin: 0;
  padding: 0;
}
@media (max-width: 700px) {
  body.profile .psponsors { display: none; }
}

/* The board: a card, one line, the newest payments, the table.

   A row is four cells on one grid: the rank at the left edge inside the card,
   the face, who they are and what they said, then the amount at the right
   edge. The row is 56 tall whether it carries one line or two, nothing is
   hidden and nothing is clipped at either end. The day a spot frees up is not
   on the row: every row of a fixed board frees up on the same day, so the
   line over the table says it once. */
body.profile .boardline { margin: 0 0 8px; font-size: var(--body); }

body.dash .boardtable,
body.profile .boardtable { display: block; width: 100%; border-collapse: collapse; }
body.dash .boardtable tbody,
body.profile .boardtable tbody { display: block; }

body.dash .brow,
body.profile .brow {
  display: grid;
  grid-template-columns: 24px 32px minmax(0, 1fr) 88px;
  align-items: center;
  column-gap: 8px;
  min-height: 56px;
  border-top: 1px solid var(--hairline);
}

body.dash .brow:first-child,
body.profile .brow:first-child { border-top: 0; }

body.dash .boardtable td,
body.profile .boardtable td {
  display: block;
  height: auto;
  min-width: 0;
  padding: 4px 0;
  border: 0;
  white-space: normal;
}

body.dash .brank,
body.profile .brank { color: var(--muted); font-size: var(--help); }
body.dash .bpic .avatar,
body.profile .bpic .avatar { display: block; width: 32px; height: 32px; border-radius: 50%; }
body.dash .bname,
body.profile .bname { display: flex; align-items: center; gap: 8px; min-width: 0; flex-wrap: nowrap; }

/* The handle is the row's own name, and below 1024 it is never cut: the media
   query at the end of this block gives it a line of its own, and 15 characters
   is the longest handle X hands out. The clamp is kept for the wide layout,
   where the handle and the pill do share one line and a long handle would
   otherwise push the pill out of the cell. */
body.dash .bhandle,
body.profile .bhandle {
  font-family: var(--mono);
  font-size: var(--table);
  color: var(--ink);
  text-decoration: none;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.dash .bname .pill,
body.profile .bname .pill { flex: none; }
body.dash .bhandle:hover,
body.profile .bhandle:hover { text-decoration: underline; }

/* The sentence and the mark after it are two boxes side by side, not one box
   with the mark at the end of it. Only .btext is cut, and the mark is a flex
   item of its own that never shrinks: cut as one line the mark is the last
   thing in it, so a long sentence takes the sponsor's paid link with it. */
body.dash .bline,
body.profile .bline {
  display: flex;
  align-items: baseline;
  gap: 4px;
  margin-top: 4px;
  min-width: 0;
  font-size: var(--help);
  color: var(--muted);
}

body.dash .btext,
body.profile .btext {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The mark is a target as well as a picture: padding takes it past 24 across
   and 24 down for a finger, and the same measure comes off the top and the
   bottom again as a negative margin, so the target is bigger than the arrow
   and the row is still 56 tall. */
body.dash .bline a.ext,
body.profile .bline a.ext {
  flex: none;
  display: inline-block;
  min-width: 24px;
  padding: 4px;
  margin: -4px 0;
}

body.dash .bline a,
body.profile .bline a { color: var(--muted); }
body.dash .bamt,
body.profile .bamt { text-align: right; font-family: var(--mono); font-size: var(--table); white-space: nowrap; }
body.dash .bopen,
body.profile .bopen { color: var(--muted); font-size: var(--body); }

/* The face a spot nobody has bought has: the site's own ground in the circle
   the picture would fill, so the row is the height of a row and the word
   beside it starts where a handle does. */
body.dash .bslot,
body.profile .bslot {
  display: block;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--ground);
}
body.profile .brow-mine { background: var(--row-hover); }
body.dash .bempty,
body.profile .bempty { grid-column: 1 / -1; }

/* A row that has just been paid for: it arrives at the height it keeps, fades
   in over 300ms and holds a light blue ground for two seconds, which
   public/spot.js takes off again. Both times are the motion tokens, and a
   reader who has asked for less movement has them at 0s. */
@keyframes browin {
  from { opacity: 0; }
  to { opacity: 1; }
}

body.profile .brow-in { animation: browin var(--t-slow) linear; background: var(--row-new); }

/* Below 1024 the who cell is a two-row grid: the handle on a line of its own,
   and under it the Headline pill and the sentence side by side. The pill is 88
   wide and never shrinks, so sharing one line with it cut a phone-width handle
   to "@spot...". The markup does not move. It is still handle, pill, line, in
   that order, because a screen reader reads the order a row is written in;
   .bname stops drawing a box of its own so its two children sit on the cell's
   own grid.
   Two rows of text is 50, and the row is still 56. */
@media (max-width: 1023px) {
  body.dash .boardtable td.bwho,
  body.profile .boardtable td.bwho {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: center;
    row-gap: 4px;
    padding: 0;
  }

  body.dash .bwho .bname,
  body.profile .bwho .bname { display: contents; }
  body.dash .bwho .bhandle,
  body.profile .bwho .bhandle { grid-column: 1 / -1; grid-row: 1; }
  body.dash .bwho .bname .pill,
  body.profile .bwho .bname .pill { grid-column: 1; grid-row: 2; margin-right: 8px; }
  body.dash .bwho .bline,
  body.profile .bwho .bline { grid-column: 2; grid-row: 2; margin-top: 0; }

  /* A row carrying the Headline pill gets a third line for its own text. The
     pill is 88 wide and never shrinks, so sharing row 2 with it cut a
     phone-width run of dates to "1 to ...". Only that row grows: every other
     row has no pill and keeps the two rows above. */
  body.dash .bwho .bname:has(.pill) ~ .bline,
  body.profile .bwho .bname:has(.pill) ~ .bline { grid-column: 1 / -1; grid-row: 3; }
}

@media (max-width: 560px) {
  body.dash .brow,
  body.profile .brow { grid-template-columns: 24px 32px minmax(0, 1fr) 72px; column-gap: 4px; }
}

/* A sponsor the builder has taken off the picture. The word sits beside the
   handle, in the muted the rest of the row takes, because it is the answer to
   "why is this one not on my banner" and that is the row's own business. */
body.dash .bhidden { flex: none; color: var(--muted); font-size: var(--help); }

/* The month page is the only page this heading is drawn on. */
body.profile .monthhead { margin: 24px 0 16px; font-size: var(--h2); }

/* ------------------------------------------- the dashboard's delete rows */

body.dash .card .dangerbox {
  border: 0;
  border-radius: 0;
  padding: 0;
  margin: 0;
  max-width: none;
}

body.dash .delrow {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 0;
  border-top: 1px solid var(--hairline);
}

body.dash .card > .delrow:first-of-type,
body.dash .card > .dangerbox:first-of-type > .delrow { border-top: 0; }

body.dash .dangerbox > summary.delrow {
  font-family: var(--sans);
  font-size: var(--body);
}

/* The row is its own marker: the button says what pressing does. */
body.dash .dangerbox > summary.delrow::before,
body.dash .dangerbox[open] > summary.delrow::before { content: none; }

body.dash .delrow .help { margin: 0; }
body.dash .delrow .btn { flex: 0 0 auto; }
body.dash .delrow-form .facts { margin-top: 0; }
body.dash .delkeep {
  display: inline-flex;
  align-items: center;
  height: var(--ctl-h);
  margin-left: 16px;
  font-size: var(--btn);
}

@media (max-width: 560px) {
  body.dash .delrow { flex-wrap: wrap; }
  body.dash .delrow .btn { flex: 1 1 100%; }
}

/* ----------------------------------- a request, its facts and its thread */

.reqgrid { display: grid; grid-template-columns: 1fr; gap: 16px; align-items: start; }

@media (min-width: 1024px) {
  .reqgrid { grid-template-columns: 1.6fr 1fr; }
}

/* The card beside the thread: a label over its value, one pair per line. The
   label is over the value rather than beside it because the column is narrow
   on both pages, and a value that wraps back under its own label reads worse
   than one that never shared the line. */
.reqfacts { display: flex; flex-direction: column; gap: 12px; }
.reqfact { display: flex; flex-direction: column; }
.reqlbl { color: var(--muted); font-size: var(--help); }

/* The draft post hangs under the facts list rather than inside it, so it takes
   the same gap by hand. */
.reqcard > .reqlbl { display: block; margin: 12px 0 4px; }

/* The sponsor's page is neither a dashboard page nor a builder page, so the
   site's one card rule does not reach it. Same card, drawn here, and only
   inside this grid: nothing else on that page is a card. */
/* Prompt Q1: the box comes from `.panel`, which this page's cards carry
   like every other card on the site. Padding and the air under it stay. */
body:not(.dash) .reqgrid .card {
  /* 24 is what --card-pad is worth; the variable itself is declared on
     body.dash and body.profile, and this page is neither. */
  padding: 24px;
  margin: 0 0 24px;
}

body:not(.dash) .reqgrid .card > h2:first-child {
  font-family: var(--mono);
  font-size: var(--h2);
  /* 400 for the same reason every other mono heading is: Space Mono has no
     500 and this was drawing with the regular already. */
  font-weight: 400;
  line-height: 1.25;
  margin: 0 0 16px;
}

body:not(.dash) .reqgrid .card > :last-child { margin-bottom: 0; }

/* The thread. A system line is what happened rather than what anybody said,
   so it is muted and carries no name over it. A message keeps the line
   breaks it was typed with. */
.thread { margin: 0; padding: 0; list-style: none; }
.thread .msg { display: flex; flex-direction: column; gap: 4px; padding: 12px 0; }
.thread .msg + .msg { border-top: 1px solid var(--hairline); }
.thread .msg:first-child { padding-top: 0; }
.thread .msg:last-child { padding-bottom: 0; }
.msgwho { color: var(--muted); font-size: var(--help); font-family: var(--mono); }
.msgbody { white-space: pre-wrap; }
.thread .msg-system .msgbody { color: var(--muted); }

/* A hairline between what was said and what is about to be: the box is not
   the last message on the thread, it is the next one. */
.replyform {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--hairline);
}

/* ------------------------------------------ the inbox's box and its caps

Two widths of the inbox, written here rather than in inbox.css with the rest
of it. app-shell.css's min-width: 1200px block answers the three caps at
exactly the same weight and wins by loading later than this file; from
inbox.css they would load after the shell and take that answer back. The
phone reset sits beside the .panel it resets, which is also here. */

@media (max-width: 899px) {
  /* The inbox on a phone is one screen per pane, so it draws no box at all -
     prompt Q1 keeps that as a media rule on the panel this page carries,
     rather than as a second box style. */
  .ibx.panel { border: 0; border-radius: 0; background: none; box-shadow: none; }
}

/* Three panes, and the wider shell that holds them. The shell answers both
   of these from app-shell.css, which is the file that wins. */
@media (min-width: 1200px) {
  body.dash main:has(.ibx),
  body.dash header.top:has(+ main .ibx),
  body.dash main:has(.ibx) ~ footer { max-width: var(--ibx-max); }

  body.dash .dashlayout:has(.ibx) {
    grid-template-columns: var(--dashnav-w) minmax(0, 1fr);
  }
}

/* ------------------------------------------- a face and its product mark */

/* The face, with the product it is about in its corner. Drawn on the picture
   rather than beside it, so every row starts on the same x. */
.ibxface { position: relative; display: inline-flex; flex: none; }

.ibxprod {
  position: absolute;
  right: -2px;
  bottom: -2px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 1px solid var(--hairline);
  background: var(--surface);
  color: var(--muted);
}

.ibxprod svg { display: block; }

/* T0:end */

/* --------------------------------------------------------------- /mcp

   The MCP page. Outside the T0 region deliberately: nothing on this page is
   part of the dashboard and builder-page system that region governs.

   Layout (2026-09-24): the page takes the header's 960 box, like the prose
   pages, but uses all of it. The address bar, then one card with a tab per
   client, then Tools and Try it side by side. Every snippet wraps inside
   its box and never scrolls sideways, at 1440 or at 390. */

main:has(> .mcp) { max-width: 960px; }

.mcp h1 { max-width: 800px; }

.mcplede {
  color: var(--muted);
  margin: 0 0 24px;
}

/* The address. It is the one thing every client needs, so it is the first
   box on the page and the biggest type in it. */
.mcpurl {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 12px 12px 20px;
  margin: 0 0 16px;
}

.mcpurllabel {
  flex: none;
  font-size: 13px;
  color: var(--muted);
}

.mcpurltext {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--mono);
  font-size: 16px;
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* The Copy button: the address bar and every snippet head. public/copy.js
   copies the element its data-copy names and says Copied for two seconds. */
.mcpcopy {
  flex: none;
  height: 32px;
  padding: 0 12px;
  font-family: var(--sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 8px;
  cursor: pointer;
}

.mcpcopy:hover { border-color: var(--muted); }

.mcpcopy:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* The client card. The radios sit before the tab row and are hidden; each
   label in the row points at one, and the checked one lights its label and
   shows its pane. No script: this is the rule that switches them. */
.mcpconnect {
  position: relative;
  margin: 0 0 16px;
}

.mcpradio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  opacity: 0;
  pointer-events: none;
}

.mcptabs {
  display: flex;
  gap: 4px;
  padding: 0 12px;
  border-bottom: 1px solid var(--hairline);
  overflow-x: auto;
  scrollbar-width: none;
}

.mcptab {
  display: inline-flex;
  align-items: center;
  height: 48px;
  padding: 0 12px;
  margin: 0 0 -1px;
  font-family: var(--sans);
  font-size: 14px;
  color: var(--muted);
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  cursor: pointer;
}

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

#mcptab-code:checked ~ .mcptabs .mcptab-code,
#mcptab-cursor:checked ~ .mcptabs .mcptab-cursor,
#mcptab-desktop:checked ~ .mcptabs .mcptab-desktop {
  color: var(--ink);
  font-weight: 500;
  border-bottom-color: var(--accent);
}

#mcptab-code:focus-visible ~ .mcptabs .mcptab-code,
#mcptab-cursor:focus-visible ~ .mcptabs .mcptab-cursor,
#mcptab-desktop:focus-visible ~ .mcptabs .mcptab-desktop {
  outline: 2px solid var(--ink);
  outline-offset: -8px;
}

/* One pane shows at a time, at its own height. */
.mcppanes { padding: 20px 24px 24px; }

.mcppane { display: none; }

#mcptab-code:checked ~ .mcppanes .mcppane-code,
#mcptab-cursor:checked ~ .mcppanes .mcppane-cursor,
#mcptab-desktop:checked ~ .mcppanes .mcppane-desktop { display: block; }

.mcppane > p { margin: 0 0 12px; }

/* One snippet: a head row with its name and Copy, then the text. */
.mcpsnip {
  background: var(--ground);
  border: 1px solid var(--hairline);
  border-radius: 12px;
}

.mcpsniphead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 4px 4px 16px;
  border-bottom: 1px solid var(--hairline);
}

.mcpsniplabel {
  min-width: 0;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.mcppre {
  margin: 0;
  padding: 12px 16px;
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Claude Desktop's config-file route, for a build with no Connectors
   screen. Closed by default: most readers never need it. */
.mcpmore { margin: 16px 0 0; }

.mcpmore > summary {
  width: fit-content;
  font-size: 14px;
  color: var(--muted);
  cursor: pointer;
}

.mcpmore > summary:hover { color: var(--ink); }

.mcpmore[open] > summary { margin-bottom: 12px; }

.mcpmore > p { margin: 0 0 12px; }

/* Tools and Try it, side by side, the same height. */
.mcpgrid {
  display: grid;
  grid-template-columns: 3fr 2fr;
  gap: 16px;
  margin: 0 0 16px;
}

.mcpbox { padding: 20px 24px 24px; margin: 0; }

.mcpbox h2 { margin: 0 0 16px; }

.mcpbox p { margin: 0 0 12px; }

.mcpbox > :last-child { margin-bottom: 0; }

/* The three tools. A list, not a table: two lines each, the same two lines
   at every width, so a phone is never shown a column heading as a label. */
.mcptools {
  list-style: none;
  margin: 0;
  padding: 0;
}

.mcptools li {
  padding: 12px 0;
  border-top: 1px solid var(--hairline);
}

.mcptools li:first-child { padding-top: 0; border-top: 0; }

.mcptools li:last-child { padding-bottom: 0; }

.mcptool {
  display: block;
  font-size: 14px;
}

.mcptoolsay {
  display: block;
  margin-top: 4px;
  color: var(--muted);
  font-size: 14px;
}

/* The example asks, drawn as what they are: things a person types to an
   agent. */
.mcpasks {
  display: grid;
  gap: 8px;
  list-style: none;
  margin: 0;
  padding: 0;
}

.mcpasks li {
  padding: 8px 12px;
  font-size: 14px;
  line-height: 1.5;
  background: var(--ground);
  border-radius: 12px;
}

.mcp > .rule { margin: 0; }

@media (max-width: 720px) {
  .mcpgrid { grid-template-columns: 1fr; }
  .mcppanes,
  .mcpbox { padding: 16px; }
  .mcptabs { padding: 0 4px; }
  /* The label and Copy on one row, the address on the next at full width. */
  .mcpurl { flex-wrap: wrap; gap: 8px 12px; padding: 12px 12px 16px 16px; }
  .mcpurllabel { flex: 1 1 auto; }
  .mcpurltext { order: 3; flex-basis: 100%; font-size: 15px; }
}

/* The smallest phones. The domain in the h1 is one word that cannot wrap,
   and the three tabs have to fit without scrolling their row. */
@media (max-width: 380px) {
  .mcp h1 { font-size: 22px; }
  .mcptab { padding: 0 6px; font-size: 13px; }
}

/* ----------------------------------------------------------- /results

   What sponsors got. The h1 is the running total of views; each finished
   sponsorship is one card: the post as X drew it on the left, the numbers
   read from X on the right. One column below 800. */

main:has(> .results) { max-width: 960px; }

.results h1 { max-width: 800px; }

.reslede {
  color: var(--muted);
  margin: 0 0 24px;
}

.rescase {
  display: grid;
  grid-template-columns: minmax(0, 400px) minmax(0, 1fr);
  gap: 32px;
  align-items: start;
  padding: 24px;
  margin: 0 0 24px;
}

.resshot {
  display: block;
  border: 1px solid var(--hairline);
  border-radius: 12px;
  overflow: hidden;
  line-height: 0;
}

.resshot img {
  width: 100%;
  height: auto;
}

.resfacts h2 { margin: 0 0 4px; }

.resfacts h2 a { color: inherit; }

/* Who sponsored it and when, one muted line under the heading. */
.resby {
  margin: 0 0 16px;
  font-size: 15px;
  color: var(--muted);
}

.resstats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin: 0 0 16px;
}

.resstats > div {
  padding: 12px;
  background: var(--ground);
  border-radius: 12px;
}

.resbig {
  display: block;
  font-family: var(--mono);
  font-size: 22px;
  font-weight: 700;
  color: var(--ink);
}

.reslabel {
  display: block;
  margin-top: 2px;
  font-size: 13px;
  color: var(--muted);
}

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

.resnext {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px;
}

@media (max-width: 800px) {
  .rescase { grid-template-columns: minmax(0, 1fr); gap: 24px; padding: 16px; }
  .resshot { max-width: 480px; }
}

@media (max-width: 420px) {
  .resbig { font-size: 18px; }
  .resstats > div { padding: 10px 8px; }
}

/* The ledger under the cards (was /bookings). */
.resledger { margin: 40px 0 32px; scroll-margin-top: 16px; }

.resledger h2 { margin: 0 0 16px; }

/* ------------------------------------------------------------------ /faq

   The sponsor FAQ (2026-09-25). One row per question, hairline between rows,
   every row closed until pressed. The question is an <h2> inside the
   <summary>, so the whole row is the button and the page still reads as ten
   headings. The plus is two bars drawn with gradients; open, the mark turns
   a quarter and the bar that ends up upright shrinks to nothing, so it is a
   minus (the turn and the shrink are timed in the motion section). The answer and its "See" line sit under the
   question, in the reading column. public/faq.js opens the row a link
   points at (/faq#who-gets-my-money). */

.faqlist {
  margin: 24px 0 32px;
  border-top: 1px solid var(--hairline);
}

.faqitem {
  border-bottom: 1px solid var(--hairline);
  scroll-margin-top: 16px;
}

.faqitem > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 56px;
  padding: 16px 0;
  cursor: pointer;
  list-style: none;
}

.faqitem > summary::-webkit-details-marker { display: none; }

.faqitem > summary h2 {
  margin: 0;
  font-size: 17px;
  line-height: 1.35;
  color: var(--ink);
}

.faqitem > summary::after {
  content: "";
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  color: var(--muted);
  background:
    linear-gradient(currentColor, currentColor) center / 14px 2px no-repeat,
    linear-gradient(currentColor, currentColor) center / 2px 14px no-repeat;
}

.faqitem[open] > summary::after {
  background:
    linear-gradient(currentColor, currentColor) center / 0 2px no-repeat,
    linear-gradient(currentColor, currentColor) center / 2px 14px no-repeat;
  transform: rotate(90deg);
}

.faqitem > summary:hover h2,
.faqitem > summary:hover::after { color: var(--accent); }

.faqitem > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.faqans { padding: 0 0 20px; }

.faqans > .rule { margin: 0; }

/* ---------------------------------------------------------------- motion

   The site-wide micro-interactions (2026-09-25): what a reader does to move
   around the site answers them, quietly. Outside the T0 region deliberately:
   these are the whole site's rules, not the dashboard system's, and the
   dashboard and admin keep the press app-shell.css gives them.

   Every duration here is a token, and the reduced-motion block at the top of
   this file zeroes all four. What a zero duration cannot reach (the page
   crossfade, smooth scrolling, a transform held as a state) is only turned on
   under no-preference, or taken back off in the block at the end. */

/* 1. Page to page. A browser with cross-document view transitions fades the
      page it is leaving into the one it lands on; any other browser simply
      navigates. The header is its own layer and is not animated, so it holds
      still while the page under it fades: the new header is drawn at once
      and the old one is never shown. The header is not sticky anywhere on
      the site, so its layer is only ever the top of the page. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }

  /* Smooth only after the page has loaded: public/ui.js puts `smooth` on
     the root one frame after the load event. A plain `html` rule also
     smoothed the very first scroll to a #fragment after a load, so sorting
     the directory (a reload of /?sort=...#builders) glided down from the
     hero instead of opening on the table. (html:focus-within was tried
     first; Chrome matches it on a fresh document with nothing focused.) */
  html.smooth { scroll-behavior: smooth; }
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 180ms;
  animation-timing-function: ease-out;
}

header.top { view-transition-name: header; }

::view-transition-group(header),
::view-transition-new(header) { animation: none; }

::view-transition-old(header) { display: none; }

/* 2. The header's links and the footer's columns. An underline that grows
      from the left under the pointer; in the header it is the tab bars'
      2px accent and it stays under the page the reader is on (layout() in
      views.js writes aria-current="page" on that link). The header link is
      padded 4px underneath for the line and gives the 4px back as a negative
      margin, so the header is exactly as tall as it was. */
header.top nav a {
  padding-bottom: 4px;
  margin-bottom: -4px;
  background: linear-gradient(var(--accent), var(--accent)) no-repeat 0 100% / 0 2px;
  transition: background-size var(--t-fast) ease-out, color var(--t-fast) ease-out;
}

header.top nav a:hover,
header.top nav a[aria-current="page"] { background-size: 100% 2px; }

header.top nav a[aria-current="page"] { color: var(--ink); }

.footcol a {
  background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0 1px;
  transition: background-size var(--t-fast) ease-out, color var(--t-fast) ease-out;
}

.footcol a:hover { background-size: 100% 1px; }

/* 3. The press. A button lifts a pixel under the pointer and goes a pixel
      down while it is held, the same pixel the home page, the builder page
      and the dashboard already press by. The dashboard and the admin keep
      their own hover (a darker fill, no lift). The focus ring is untouched. */
body:not(.dash):not(.admin) :is(a.btn, button.btn, input[type="submit"]):not(.linkish):not(:disabled) {
  transition: background var(--t-quick) ease-out, border-color var(--t-quick) ease-out,
    opacity var(--t-quick) ease-out, filter var(--t-quick) ease-out,
    transform var(--t-quick) ease-out;
}

body:not(.dash):not(.admin) :is(a.btn, button.btn, input[type="submit"]):not(.linkish):not(:disabled):hover {
  transform: translateY(-1px);
}

body:not(.dash):not(.admin) :is(a.btn, button.btn, input[type="submit"]):not(.linkish):not(:disabled):active {
  transform: translateY(1px);
}

/* 4. In-page anchors. Smooth scrolling is switched on with the crossfade
      above, under no-preference only. The header is not sticky, so the
      margin is the breathing room above a section a link lands on, not room
      for a bar over it. */
main [id] { scroll-margin-top: 16px; }

/* 5. The FAQ rows open and close rather than snap, and the plus turns into
      the minus. The height only animates where a browser can animate to
      auto; anywhere else the row snaps open as it always has. */
.faqitem > summary::after {
  transition: transform var(--t-fast) ease-out, background-size var(--t-fast) ease-out;
}

@supports (interpolate-size: allow-keywords) {
  .faqlist { interpolate-size: allow-keywords; }

  .faqitem::details-content {
    block-size: 0;
    opacity: 0;
    overflow: clip;
    transition: block-size var(--t-mid) ease-out, opacity var(--t-mid) ease-out,
      content-visibility var(--t-mid) allow-discrete;
  }

  .faqitem[open]::details-content {
    block-size: auto;
    opacity: 1;
  }
}

/* 6. The count-up on the /results and /worth hero numbers is public/ui.js,
      which wraps the number in .countup while it rolls. The digits hold the
      final number's width, right-aligned, so the words after them do not
      move. */
.countup.is-rolling {
  display: inline-block;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* 7. The directory's rows. The ground already fades (landing.css, on
      --t-quick); the face in the row grows a little with it. A transform, so
      nothing around it moves. */
.dirlist td.rowav .avatar { transition: transform var(--t-quick) ease-out; }

.dirlist tbody tr:hover td.rowav .avatar { transform: scale(1.04); }

/* Nothing held as a state for a reader who asked for no movement: the
   durations are already zero, so this is the lift, the press and the face. */
@media (prefers-reduced-motion: reduce) {
  body:not(.dash):not(.admin) :is(a.btn, button.btn, input[type="submit"]):not(.linkish):not(:disabled):hover,
  body:not(.dash):not(.admin) :is(a.btn, button.btn, input[type="submit"]):not(.linkish):not(:disabled):active,
  .dirlist tbody tr:hover td.rowav .avatar { transform: none; }
}

/* 8. The Copy buttons (paint7). public/copy.js wraps the word in .copyword
      and swaps it in two halves of --t-fast: the old word lifts 4px and
      fades, the new one starts 4px low and comes up into place. The button
      holds the width of its wider word, so nothing beside it moves. */
.copyword {
  display: inline-block;
  transition: transform calc(var(--t-fast) / 2) ease-out, opacity calc(var(--t-fast) / 2) ease-out;
}

.copyword.is-out { transform: translateY(-4px); opacity: 0; }
.copyword.is-in { transform: translateY(4px); opacity: 0; transition: none; }

@media (prefers-reduced-motion: reduce) {
  .copyword,
  .copyword.is-out,
  .copyword.is-in { transform: none; opacity: 1; transition: none; }
}
