/* listWright.

   One stylesheet, no build step, no webfont. The content security policy this
   site serves allows fonts from 'self' only, and there are none embedded, so
   the type is the reader's own system face — which also means no flash of
   unstyled text and nothing to download before the first paint.

   The rules the design holds itself to, so a later edit knows what it is
   joining:

     no gradients          a surface is one colour. Depth comes from the
                           colour itself and from space around it, never from
                           a glow or a fade
     no decorative shadow  a tile is told apart from the page by its fill
     one accent            used for the action and for links, nothing else
     space does the work   the gap between two sections is what says they are
                           two sections

   Layout of the file:

     tokens        colour, radius, type, measure
     base          reset, headings, links, focus
     shell         masthead, main, footer
     surfaces      tiles, the dark panel
     controls      buttons, fields, banners
     marketing     hero, figures, features, steps, faq, cta
     application   panels, tables, the product tracker, scores
     conditions    reduced motion, print */

/* ---------- tokens ---------- */

:root {
  color-scheme: light;

  /* Two page colours and no more: white, and the grey a tile is filled with.
     Warm rather than neutral, because the accent is warm and a cold grey
     under it turns the orange pink. */
  --paper: #ffffff;
  --raise: #ffffff;
  --sunken: #f5f3f0;
  --ink: #1a1714;
  --muted: #6b645b;
  --faint: #938b80;
  --rule: #e6e2dc;
  --rule-firm: #cfc9c0;

  /* One accent, for the action and for links. Nothing else on the page is
     allowed to be this colour. */
  --accent: #c14a1c;
  --accent-lift: #a83f16;
  --accent-ink: #ffffff;
  --accent-wash: #f7ece7;

  /* The dark panel the hero and the closing invitation are drawn on. Flat,
     and the same in both schemes: the page has one fixed point that does not
     move under a reader who changes their mind. */
  --night: #1c1815;
  --night-tile: #292420;
  --night-ink: #f7f4ef;
  --night-muted: #a99f92;
  --night-rule: #38312b;

  /* state */
  --ok: #256b44;
  --bad: #a5331d;
  --wait: #7a6318;
  --danger: #a5331d;
  --ok-wash: #eaf3ee;
  --bad-wash: #fbecea;
  --wait-wash: #f7f1e2;

  /* shape */
  --r-xs: 6px;
  --r-sm: 10px;
  --r: 14px;
  --r-lg: 18px;
  --r-xl: 24px;
  --r-pill: 980px;

  /* Kept as tokens so nothing has to invent one, and deliberately almost
     nothing: a tile is separated from the page by its fill, not by a shadow.
     The two that exist are for things that genuinely float. */
  --shadow-1: 0 1px 2px rgba(26, 23, 20, 0.04);
  --shadow-2: 0 1px 2px rgba(26, 23, 20, 0.04), 0 12px 28px -14px rgba(26, 23, 20, 0.22);
  --shadow-3: 0 8px 32px rgba(26, 23, 20, 0.14);

  /* type. One family, worked hard: weight and tracking carry the hierarchy,
     which is what a system face is good at. */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
    Inter, Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --display: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Display",
    "Segoe UI", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  /* Two widths, and not the same number by accident. The shell is as wide as
     the grid wants to be; the measure is where a line of running text stops
     being comfortable, at roughly 70 characters. */
  --shell: 72rem;
  --measure: 38rem;
  --gutter: 1.5rem;

  /* the space between one section and the next, in one place */
  --sec: clamp(5.5rem, 10vw, 10rem);
}

@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;

    --paper: #0e0d0c;
    --raise: #171514;
    --sunken: #1c1a18;
    --ink: #f3efe9;
    --muted: #a49b90;
    --faint: #857d73;
    --rule: #2a2724;
    --rule-firm: #3d3833;

    --accent: #f07a45;
    --accent-lift: #f79366;
    --accent-ink: #1a0d06;
    --accent-wash: #241813;

    --ok: #63bd8b;
    --bad: #ef8f77;
    --wait: #d2bd6e;
    --danger: #ef8f77;
    --ok-wash: #14201a;
    --bad-wash: #241614;
    --wait-wash: #211d12;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 1px 2px rgba(0, 0, 0, 0.4), 0 12px 28px -14px rgba(0, 0, 0, 0.7);
    --shadow-3: 0 8px 32px rgba(0, 0, 0, 0.6);
  }

  /* pale glyphs on a dark ground bloom, and thinning them back is the fix
     that does not involve picking a second set of colours */
  body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }
}

/* ---------- base ---------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /* the masthead is sticky, so anything linked to by a fragment has to clear
     it rather than land under it */
  scroll-padding-top: 5.5rem;
}

body {
  background: var(--paper);
  color: var(--ink);
  font: 1rem/1.6 var(--sans);
  font-kerning: normal;
  margin: 0;
  /* a phone in landscape should not be able to drag the page sideways past a
     wide table */
  overflow-x: hidden;
}

::selection { background: var(--accent-wash); color: var(--ink); }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 650;
  letter-spacing: -0.021em;
  line-height: 1.1;
  margin: 0;
  text-wrap: balance;
}

h1 {
  font-size: clamp(2.35rem, 1.45rem + 3.7vw, 4.25rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.04;
  /* the only heading with a margin of its own. Every h2 and h3 on this site
     sits in a block that decides its own spacing, but an h1 is always
     followed by running text and was landing on top of it. */
  margin-bottom: 1rem;
}

h2 {
  font-size: clamp(1.7rem, 1.25rem + 1.9vw, 2.75rem);
  font-weight: 700;
  letter-spacing: -0.027em;
}

h3 { font-size: 1.12rem; font-weight: 650; letter-spacing: -0.016em; }
h4 { font-size: 0.95rem; font-weight: 650; }

/* pretty, not balance, for running text: balance evens the last few lines of
   a short block, which is what a heading wants. A paragraph only wants to be
   spared a one-word last line. */
p { margin: 0 0 1rem; text-wrap: pretty; }

/* Underlined on hover rather than at rest. A page of running text with a link
   in every other sentence is a page with a rash. */
a {
  color: var(--accent);
  text-decoration: none;
  text-underline-offset: 3px;
}

a:hover { text-decoration: underline; }

strong { font-weight: 620; }

/* One ring, defined once, for everything that takes focus. Keyboard users get
   it and mouse users do not, which is the whole point of focus-visible. */
:focus-visible {
  border-radius: var(--r-xs);
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

img, svg { max-width: 100%; }

hr {
  background: var(--rule);
  border: 0;
  height: 1px;
  margin: 2.5rem 0;
}

code {
  background: var(--sunken);
  border-radius: var(--r-xs);
  font-family: var(--mono);
  font-size: 0.85em;
  padding: 0.1em 0.35em;
}

/* ---------- utilities ---------- */

.muted { color: var(--muted); }
.faint { color: var(--faint); }
.small { font-size: 0.875rem; }

.mono {
  font-family: var(--mono);
  font-size: 0.85em;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.hint {
  color: var(--faint);
  font-size: 0.845rem;
  line-height: 1.55;
  margin: 0.4rem 0 0;
}

/* the label above a section heading. Set in the accent at reading size rather
   than as spaced-out capitals: small caps at 0.7rem is a decoration, and this
   is a line somebody is meant to read. */
.eyebrow {
  color: var(--accent);
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  margin: 0 0 0.5rem;
}

/* an identifier is not a label: it keeps the case it was issued with */
.eyebrow.mono { font-size: 0.8rem; }

.spacer { flex: 1 1 auto; }

.copysource { display: none; }

/* The first thing in the tab order, invisible until it is reached. Without it,
   getting to the content of a page means tabbing past the whole masthead on
   every single load. */
.skip {
  background: var(--raise);
  border: 1px solid var(--accent);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-3);
  color: var(--ink);
  left: 0.75rem;
  padding: 0.6rem 1rem;
  position: fixed;
  top: -5rem;
  transition: top 0.15s ease;
  z-index: 60;
}

.skip:focus { top: 0.75rem; }

/* ---------- masthead ---------- */

/* The announcement strip, shown to signed-out visitors only. It carries the
   one fact that decides whether somebody starts: what they get, and that
   finding out costs nothing. */
.announce {
  background: var(--sunken);
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.83rem;
  text-align: center;
}

.announce-in {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.55rem;
  justify-content: center;
  margin: 0 auto;
  max-width: var(--shell);
  padding: 0.5rem var(--gutter);
}

.announce a { font-weight: 600; white-space: nowrap; }

.announce .dot {
  background: var(--rule-firm);
  border-radius: 50%;
  height: 3px;
  width: 3px;
}

header.masthead {
  backdrop-filter: saturate(1.4) blur(16px);
  -webkit-backdrop-filter: saturate(1.4) blur(16px);
  background: color-mix(in srgb, var(--paper) 80%, transparent);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 50;
}

/* a browser without backdrop-filter gets the opaque page colour rather than a
   translucent strip with the content showing through it */
@supports not (backdrop-filter: blur(1px)) {
  header.masthead { background: var(--paper); }
}

.mast {
  align-items: center;
  display: flex;
  gap: 0.5rem 1.5rem;
  margin: 0 auto;
  max-width: var(--shell);
  padding: 0.7rem var(--gutter);
}

.wordmark {
  align-items: center;
  color: var(--ink);
  display: inline-flex;
  font-family: var(--display);
  font-size: 1.08rem;
  font-weight: 650;
  letter-spacing: -0.035em;
  text-decoration: none;
}

.wordmark:hover { text-decoration: none; }

.wordmark::after {
  background: var(--accent);
  border-radius: 50%;
  content: "";
  display: inline-block;
  height: 5px;
  margin: 0 0 0.1rem 2px;
  width: 5px;
}

nav {
  align-items: center;
  display: flex;
  flex: 1 1 auto;
  gap: 0.1rem;
  min-width: 0;
}

/* Logging in, signing up, and who you are signed in as. A group of its own
   rather than the tail of the nav, so it keeps the top row to itself when the
   links turn into a scrolling strip. */
.mast-end {
  align-items: center;
  display: flex;
  gap: 0.35rem;
  margin-left: auto;
}

.mast-end a:not(.button) {
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: 0.875rem;
  font-weight: 500;
  padding: 0.38rem 0.6rem;
  white-space: nowrap;
}

.mast-end a:not(.button):hover { color: var(--ink); text-decoration: none; }

nav a {
  border-radius: var(--r-sm);
  color: var(--muted);
  font-size: 0.875rem;
  font-weight: 500;
  padding: 0.38rem 0.6rem;
  text-decoration: none;
  transition: color 0.14s ease;
  white-space: nowrap;
}

nav a:hover { color: var(--ink); text-decoration: none; }

nav a.on { color: var(--ink); font-weight: 600; }

nav a.button, nav a.button:hover { color: var(--accent-ink); }

.who {
  color: var(--faint);
  font-size: 0.8rem;
  max-width: 14rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

form.inline { display: inline; margin: 0; }

/* Under about 60rem the nav becomes its own scrollable strip below the
   wordmark. Every link stays one tap away, which is what a menu behind a
   button costs, and there is no script to load or duplicate markup to keep in
   step. */
@media (max-width: 60rem) {
  .mast { flex-wrap: wrap; padding-bottom: 0; row-gap: 0; }

  .wordmark { order: 1; }

  /* the actions keep the top row, beside the wordmark */
  .mast-end { order: 2; }
  .who { display: none; }

  nav {
    -webkit-overflow-scrolling: touch;
    flex-basis: 100%;
    order: 3;
    overflow-x: auto;
    padding: 0.15rem 0 0.55rem;
    scrollbar-width: none;
    width: 100%;
  }

  nav::-webkit-scrollbar { display: none; }

  /* a strip that scrolls should not be flush with the edge of the screen at
     either end, or the first and last link look clipped rather than scrollable */
  nav::after { content: ""; flex: none; width: 0.5rem; }
}

/* ---------- shell ---------- */

main {
  margin: 0 auto;
  max-width: var(--shell);
  padding: 3rem var(--gutter) 5rem;
}

/* the marketing pages open on a panel that supplies its own top space */
main.page-home { padding-top: 1.25rem; }

/* The measure belongs to running text, not to everything sharing the page with
   it. Capping the whole .prose block was capping the tables inside it too,
   which is how a six-column reference table ended up a third of the window
   wide with its last marketplace clipped off the right edge. Cap what reads as
   prose, and forms with it, since a form is not improved by running wider than
   the sentence above it. A table, a panel or a worked example then has the
   shell it is already sitting in.

   Headings are deliberately not in that list. A heading is display type, set
   once and read at a glance, not a paragraph read line after line; holding one
   to the same 38rem was breaking "Listing limits on Etsy, Shopify, eBay and
   Amazon" across three lines for no reason. */
.prose > p,
.prose > ul,
.prose > ol,
.prose > form { max-width: var(--measure); }

/* Safari pulls the opening quote or bracket of a line into the margin so the
   left edge stays optically straight. Everywhere else this does nothing. */
.prose, .standfirst { hanging-punctuation: first last; }

.prose p, .prose li { color: var(--muted); }
.prose li { margin-bottom: 0.4rem; }

.prose h2 { margin: 2.75rem 0 0.7rem; scroll-margin-top: 6rem; }
.prose h3 { margin: 1.9rem 0 0.5rem; }

.standfirst {
  color: var(--muted);
  font-size: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);
  letter-spacing: -0.011em;
  line-height: 1.5;
  max-width: 44rem;
}

.column { margin: 0 auto; max-width: 26rem; }

/* A table of tenants or runs wants the opposite of a form's narrow measure,
   and widening .column itself would stretch six forms to fix two pages. */
.column.wide { max-width: 62rem; }

/* A heading that starts a new block after running text, outside .prose. */
h2.section {
  border-top: 1px solid var(--rule);
  margin: 3rem 0 0.5rem;
  padding-top: 1.75rem;
}

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

footer {
  border-top: 1px solid var(--rule);
  color: var(--faint);
  font-size: 0.85rem;
  margin-top: 4rem;
}

.foot {
  display: grid;
  gap: 2.25rem 3rem;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  margin: 0 auto;
  max-width: var(--shell);
  padding: 3rem var(--gutter) 2.25rem;
}

.foot-brand .wordmark { font-size: 1rem; }

.foot-brand p {
  color: var(--faint);
  font-size: 0.85rem;
  margin: 0.7rem 0 0;
  max-width: 22rem;
}

.foot h4 {
  color: var(--ink);
  font-size: 0.85rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  margin: 0 0 0.8rem;
}

.foot ul { list-style: none; margin: 0; padding: 0; }
.foot li { margin: 0 0 0.5rem; }

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

/* the footer's own copy of the fine print: no rule of its own, since the
   footer already has one above it */
footer .fineprint {
  border-top: 1px solid var(--rule);
  margin: 0 auto;
  max-width: var(--shell);
  padding: 1.5rem var(--gutter) 2.5rem;
}

footer .fineprint p {
  color: var(--faint);
  font-size: 0.78rem;
  line-height: 1.6;
  margin: 0;
  max-width: 54rem;
}

@media (max-width: 52rem) {
  .foot { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .foot-brand { grid-column: 1 / -1; }
}

@media (max-width: 30rem) {
  .foot { gap: 1.75rem; grid-template-columns: 1fr; }
}

/* ---------- surfaces ---------- */

/* The one card, and it is a card because it holds something that came out of
   the product: a sheet, a listing, a run. It is told apart from the page by a
   hairline and a shadow you would not notice if it were missing, which is the
   point of both. */
.card {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 1.6rem;
}

a.card, .card.lift {
  color: inherit;
  display: block;
  text-decoration: none;
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

a.card:hover, .card.lift:hover {
  border-color: var(--rule-firm);
  box-shadow: var(--shadow-2);
  text-decoration: none;
}

/* a run of sections down a page, each with air around it */
.sec { margin-top: var(--sec); }

/* One section on the grey rather than the white. Used once or twice a page at
   most: it is the only structural device here besides space and a hairline,
   and a page where every other section is tinted is a page of stripes. */
.sec.tint {
  background: var(--sunken);
  border-radius: var(--r-lg);
  padding: clamp(2.5rem, 5vw, 4rem) clamp(1.5rem, 4vw, 3.5rem);
}

.sec.tint .card, .sec.tint .tier, .sec.tint .split > * {
  background: var(--raise);
}

.sec-head { margin: 0 0 3.25rem; max-width: 46rem; }

/* Centred only where a section is a chapter break rather than an explanation.
   Everything else lines up with the left edge of what it introduces, because a
   heading indented from its own content reads as a caption for the section
   above it. */
.sec-head.centre { margin-inline: auto; text-align: center; }
.sec-head p { color: var(--muted); margin: 0.85rem 0 0; }
.sec-head > :last-child { margin-bottom: 0; }

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

button,
.button {
  align-items: center;
  background: var(--accent);
  border: 1px solid var(--accent);
  border-radius: var(--r-pill);
  color: var(--accent-ink);
  cursor: pointer;
  display: inline-flex;
  font: 600 0.925rem/1.25 var(--sans);
  gap: 0.4rem;
  justify-content: center;
  letter-spacing: -0.008em;
  padding: 0.6rem 1.25rem;
  text-decoration: none;
  transition: background-color 0.16s ease, border-color 0.16s ease,
    color 0.16s ease;
  white-space: nowrap;
}

/* Darkening the accent rather than fading the whole button out. A button at
   88% opacity is a button showing you the page through itself. */
button:hover, .button:hover {
  background: var(--accent-lift);
  border-color: var(--accent-lift);
  text-decoration: none;
}

button.ghost, .button.ghost {
  background: transparent;
  border-color: var(--rule-firm);
  color: var(--ink);
}

button.ghost:hover, .button.ghost:hover {
  background: var(--sunken);
  border-color: var(--rule-firm);
  color: var(--ink);
}

/* a third weight, for an action that is available but is not what the page is
   asking for */
button.plain, .button.plain {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
}

button.plain:hover, .button.plain:hover {
  background: transparent;
  border-color: transparent;
  color: var(--ink);
}

/* A destructive action has to look different from an ordinary one, and it is
   an outline rather than a solid block on purpose: the filled button is the
   one people press without reading. */
button.danger, .button.danger {
  background: transparent;
  border-color: var(--rule-firm);
  color: var(--danger);
}

button.danger:hover, .button.danger:hover {
  background: var(--danger);
  border-color: var(--danger);
  color: var(--paper);
}

button.big, .button.big { font-size: 1rem; padding: 0.78rem 1.6rem; }

button.small, .button.small { font-size: 0.82rem; padding: 0.32rem 0.8rem; }

.actions {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 0.65rem;
  margin: 1.75rem 0 0;
}

label {
  display: block;
  font-size: 0.86rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  margin: 1.25rem 0 0.4rem;
}

input[type="email"],
input[type="password"],
input[type="search"],
input[type="text"],
textarea,
select {
  accent-color: var(--accent);
  background: var(--raise);
  border: 1px solid var(--rule-firm);
  border-radius: var(--r-sm);
  caret-color: var(--accent);
  color: var(--ink);
  font: inherit;
  padding: 0.66rem 0.85rem;
  transition: border-color 0.14s ease;
  width: 100%;
}

input::placeholder, textarea::placeholder { color: var(--faint); }

input[type="search"]::-webkit-search-cancel-button {
  cursor: pointer;
  /* the platform's own is a grey disc that reads as a different design's
     control sitting inside this one */
  filter: grayscale(1) opacity(0.5);
}

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

/* The arrow is drawn rather than left to the platform, whose own is grey on
   grey chrome that sits oddly against everything else here. A data uri rather
   than a file, since a separate request for an arrow is a poor trade;
   currentColor cannot be used inside one, so the ink is written in and the
   dark scheme overrides it. */
select {
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%236b645b' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-position: right 0.85rem center;
  background-repeat: no-repeat;
  background-size: 0.65rem;
  cursor: pointer;
  padding-right: 2.3rem;
}

@media (prefers-color-scheme: dark) {
  select {
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23a49b90' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  }
}

/* Without this a textarea falls back to the platform's own box, which on this
   page means a small white monospace panel among the styled inputs. Only the
   height and the resize handle are its own. */
textarea {
  line-height: 1.55;
  min-height: 8rem;
  /* horizontal dragging breaks the column it sits in; vertical is the one
     somebody pasting a long description actually wants */
  resize: vertical;
}

input[type="file"] {
  background: var(--sunken);
  border: 1px dashed var(--rule-firm);
  border-radius: var(--r);
  color: var(--muted);
  cursor: pointer;
  font-size: 0.9rem;
  padding: 1.5rem 1.1rem;
  transition: border-color 0.14s ease, background-color 0.14s ease;
  width: 100%;
}

/* the drop target is the whole control, so it should say so */
input[type="file"]:hover {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--ink);
}

form.stack button { margin-top: 1.75rem; width: 100%; }

/* A form long enough to need numbered parts needs the parts told apart. */
form.stack h2 {
  border-top: 1px solid var(--rule);
  font-size: 1.2rem;
  margin: 2.5rem 0 0.4rem;
  padding-top: 1.5rem;
}

form.stack h2:first-of-type { border-top: 0; margin-top: 0.5rem; padding-top: 0; }

.stack fieldset { border: 0; margin: 0; padding: 0; }

.stack legend {
  font-size: 0.86rem;
  font-weight: 600;
  margin-bottom: 0.7rem;
  padding: 0;
}

/* A checkbox beside what it means, with the explanation under the label
   rather than beside the box, so a long hint does not wrap into the tick. */
.check {
  align-items: start;
  display: grid;
  font-weight: 400;
  gap: 0.6rem;
  grid-template-columns: auto 1fr;
  margin: 0 0 0.9rem;
}

.check input { margin-top: 0.3rem; }
.check .hint { display: block; margin: 0.15rem 0 0; }

/* ---------- banners ---------- */

.banner {
  border-radius: var(--r);
  font-size: 0.92rem;
  margin: 0 0 1.75rem;
  padding: 0.85rem 1.1rem;
}

.banner.error { background: var(--bad-wash); color: var(--bad); }
.banner.notice { background: var(--ok-wash); color: var(--ok); }
.banner strong { color: inherit; }

/* The one place on this site that prints a credential: marked with the accent
   because it is the only thing on the page that has to be acted on before
   leaving it. */
.callout {
  background: var(--sunken);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r) var(--r) 0;
  margin: 1.75rem 0;
  padding: 1.2rem 1.35rem;
}

.callout p { margin: 0 0 0.6rem; }
.callout p:last-child { margin-bottom: 0; }

/* A key is fifty characters with no spaces in it, so it will not wrap on its
   own and takes a narrow layout with it. Breaking mid-token is wrong for prose
   and right here: every character matters and none of them are words.
   user-select makes one click take the whole thing. */
.secret {
  background: var(--raise);
  border-radius: var(--r-xs);
  display: block;
  font-family: var(--mono);
  font-size: 0.9rem;
  overflow-wrap: anywhere;
  padding: 0.6rem 0.7rem;
  user-select: all;
  word-break: break-all;
}

/* One statement pulled out and set on its own, for the claim a block is
   really making. */
.claim {
  background: var(--sunken);
  border-radius: var(--r);
  margin: 2rem 0 0;
  max-width: 46rem;
  padding: 1.3rem 1.5rem;
}

.claim p { margin: 0; }
.claim p + p { color: var(--muted); margin-top: 0.7rem; }

/* the fine print between sections: a disclaimer, or a note about how paying
   works */
.fineprint {
  border-top: 1px solid var(--rule);
  margin-top: var(--sec);
  max-width: 48rem;
  padding-top: 1.75rem;
}

.fineprint p { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.8rem; }
.fineprint p:last-child { margin-bottom: 0; }

/* ---------- pills and tags ---------- */

.pill {
  align-items: center;
  background: color-mix(in srgb, currentColor 12%, transparent);
  border-radius: var(--r-pill);
  display: inline-flex;
  font-size: 0.72rem;
  font-weight: 600;
  gap: 0.3rem;
  letter-spacing: 0;
  padding: 0.15rem 0.6rem;
  white-space: nowrap;
}

/* Two vocabularies share this class: what a run item is doing, and where a
   product has got to. Colouring them together is what keeps green meaning the
   same thing on both pages. */
.pill.queued, .pill.running, .pill.stale { color: var(--wait); }
.pill.validated, .pill.completed, .pill.generated { color: var(--ok); }
.pill.exported, .pill.listed { color: var(--accent); }
.pill.failed { color: var(--bad); }
.pill.skipped, .pill.cancelled, .pill.new { color: var(--faint); }

/* the tags, as the separate things they are rather than a sentence with
   commas in it */
.tagset { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; }

/* Two selectors because the markup grew two ways: the marketing pages write
   bare spans inside .tagset, while the product and demo templates label each
   one .tag. Both are a tag and both get the pill. Naming the class here is
   what stops it being an attribute nothing reads, and what keeps a tag looking
   like one if it is ever rendered outside the set. */
.tagset span,
.tag {
  background: var(--sunken);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 0.78rem;
  padding: 0.24rem 0.65rem;
}

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

/* No panel behind it, and that is the decision the rest of the page follows
   from. A headline this size on the page itself is a masthead; the same
   headline inside a big rounded rectangle is a banner ad, and a banner ad is
   the one thing the top of a home page cannot look like. */
.hero {
  margin: 0 auto;
  max-width: 54rem;
  padding: clamp(4rem, 8vw, 7.5rem) 0 0;
  text-align: center;
}

.hero .eyebrow { color: var(--muted); }
.hero h1 { margin-bottom: 1.15rem; }
.hero .standfirst { margin: 0 auto; max-width: 34rem; }
.hero .actions { justify-content: center; margin-top: 2rem; }

/* the line under the buttons: the three objections somebody has at the moment
   they decide whether to click, answered at that moment */
.reassure {
  color: var(--faint);
  display: flex;
  flex-wrap: wrap;
  font-size: 0.845rem;
  gap: 0.4rem 1.5rem;
  justify-content: center;
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

.reassure li { align-items: center; display: flex; gap: 0.45rem; }
.reassure svg { color: var(--accent); flex: none; height: 13px; width: 13px; }

/* One row of a sheet becoming one listing, in the space where a photograph of
   a laptop would otherwise go. It is drawn as a window rather than as a block
   of page: a title bar, a hairline, and content under it, because what it is
   showing is a thing the product produced rather than something this page is
   saying. */
.proof {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-2);
  margin: 3.25rem auto 0;
  max-width: 46rem;
  overflow: hidden;
  text-align: left;
}

.proof-head {
  align-items: center;
  background: var(--sunken);
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  display: flex;
  font-size: 0.82rem;
  font-weight: 600;
  gap: 0.5rem;
  margin: 0;
  padding: 0.75rem 1.4rem;
}

.proof-head .pill { margin-left: auto; }

.proof dl {
  display: grid;
  gap: 0.45rem 1.25rem;
  grid-template-columns: 6rem minmax(0, 1fr);
  margin: 0;
  padding: 1.35rem 1.4rem 0;
}

.proof dt { color: var(--faint); font-size: 0.84rem; }
.proof dd { font-size: 0.87rem; margin: 0; overflow-wrap: anywhere; }

/* the seam between what went in and what came back */
.proof-arrow {
  align-items: center;
  color: var(--faint);
  display: flex;
  font-size: 0.82rem;
  font-weight: 600;
  gap: 0.75rem;
  margin: 1.4rem 0 1rem;
  padding: 0 1.4rem;
}

.proof-arrow::after {
  background: var(--rule);
  content: "";
  flex: 1 1 auto;
  height: 1px;
}

.proof-out {
  font-size: 1.02rem;
  letter-spacing: -0.014em;
  margin: 0 1.4rem 0.85rem;
}

.proof .chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0 1.4rem;
}

.proof .chips span {
  background: var(--sunken);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 0.76rem;
  padding: 0.2rem 0.65rem;
}

.proof-note {
  background: var(--sunken);
  border-top: 1px solid var(--rule);
  color: var(--faint);
  font-size: 0.8rem;
  margin: 1.35rem 0 0;
  padding: 0.85rem 1.4rem;
}

@media (max-width: 34rem) {
  .proof dl { gap: 0; grid-template-columns: 1fr; }
  .proof dt { margin-top: 0.6rem; }
}

/* The same opening on a page that is a heading and a note beside it rather
   than an invitation. */
.hero.plain {
  display: grid;
  gap: 2rem 3.5rem;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  margin: 0;
  max-width: none;
  padding: 0 0 1rem;
  text-align: left;
}

.hero.plain .eyebrow { color: var(--accent); }
.hero.plain .standfirst { margin: 0; }
.hero.plain .actions { justify-content: flex-start; }

.hero.plain aside {
  align-self: start;
  border-top: 2px solid var(--ink);
  padding-top: 1.1rem;
}

.hero.plain aside p:last-child { margin-bottom: 0; }

@media (max-width: 52rem) {
  .hero.plain { grid-template-columns: 1fr; }
}

/* ---------- the marketplace strip ---------- */

.strip {
  align-items: center;
  color: var(--faint);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.75rem;
  justify-content: center;
  margin: 3rem 0 0;
  padding: 0;
}

.strip .label { font-size: 0.85rem; }

.strip span:not(.label) {
  color: var(--muted);
  font-family: var(--display);
  font-size: 1.05rem;
  font-weight: 650;
  letter-spacing: -0.02em;
}

/* ---------- the figures band ---------- */

/* Four facts, told apart by a rule and some space. They are not four things
   to click, so they are not four boxes. */
.figures {
  /* A hairline, not a rule. At full ink strength this was the heaviest mark on
     the page, which put the emphasis on the divider rather than on the four
     numbers under it. */
  border-top: 1px solid var(--rule-firm);
  display: grid;
  gap: 0 3rem;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: var(--sec);
  padding-top: 2.25rem;
}

.figure {
  font-family: var(--display);
  font-size: 2.6rem;
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: 650;
  letter-spacing: -0.04em;
  line-height: 1;
  margin: 0;
}

.figures .figure { font-size: 3rem; margin-bottom: 0.75rem; }

.figures p:last-child {
  color: var(--muted);
  font-size: 0.87rem;
  line-height: 1.5;
  margin: 0;
  max-width: 15rem;
}

@media (max-width: 60rem) {
  .figures { gap: 2rem 2.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 30rem) {
  .figures { grid-template-columns: 1fr; }
}

/* ---------- what it does ---------- */

/* Four claims under a hairline each, rather than four tiles. The page already
   has cards on it, and they are reserved for things that came out of the
   product or that cost money. */
.features {
  display: grid;
  gap: 2.25rem 2.5rem;
  grid-template-columns: 1fr;
  list-style: none;
  margin: 0;
  padding: 0;
}

@media (min-width: 40rem) { .features { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 68rem) { .features { grid-template-columns: repeat(4, 1fr); } }

.features li { border-top: 1px solid var(--rule); padding-top: 1.2rem; }
.features h3 { margin: 0 0 0.45rem; }
.features p { color: var(--muted); font-size: 0.9rem; margin: 0; }

.features .icon {
  align-items: center;
  color: var(--accent);
  display: flex;
  height: 1.5rem;
  margin-bottom: 0.85rem;
}

.features .icon svg { height: 20px; width: 20px; }

/* ---------- how it works ---------- */

.steps {
  counter-reset: step;
  display: grid;
  gap: 2.25rem 2.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  list-style: none;
  margin: 0;
  padding: 0;
}

.steps li { border-top: 1px solid var(--rule); padding-top: 1.2rem; }

.steps li::before {
  color: var(--accent);
  content: counter(step, decimal-leading-zero);
  counter-increment: step;
  display: block;
  font-family: var(--display);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  margin-bottom: 0.85rem;
}

.steps h2 { font-size: 1.12rem; font-weight: 650; margin-bottom: 0.45rem; }
.steps p { color: var(--muted); font-size: 0.9rem; margin: 0; }

/* ---------- the worked example ---------- */

.worked { margin-top: var(--sec); }

.worked > h2 { margin-bottom: 0.8rem; }
.worked .standfirst { margin-bottom: 0; }
.worked .split { margin-top: 1.75rem; }
.worked .eyebrow { color: var(--faint); font-size: 0.8rem; font-weight: 600; margin-top: 1.5rem; }
.worked .eyebrow:first-child { margin-top: 0; }
.worked .eyebrow .muted { color: var(--faint); font-weight: 400; }
.worked .items td:first-child { color: var(--faint); width: 7rem; }

.worked h3 {
  border-top: 1px solid var(--rule);
  margin: 2.5rem 0 0.3rem;
  padding-top: 1.6rem;
}

/* Two panels side by side: what went in, and what came back. Cards, like the
   one in the hero, and for the same reason: this is the product's output, not
   the page's argument. */
.split {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  margin-top: 2rem;
}

.split > * {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 1.5rem 1.6rem;
}

.split > * > h2 {
  border-bottom: 1px solid var(--rule);
  font-size: 1.05rem;
  margin: -0.35rem 0 1.1rem;
  padding-bottom: 0.85rem;
}

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

/* Two fields side by side inside a form is a grid, not two cards. The demo
   asks for a material and a colour on one line, and each of them arriving in
   its own bordered panel read as two separate questions. */
form .split { gap: 0 1rem; margin-top: 0; }

form .split > * {
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}

/* ---------- pricing ---------- */

/* The one place on a marketing page that is genuinely a set of boxes: three
   things to compare and pick between. Equal height, so the row of buttons at
   the bottom is a row rather than three buttons at three heights. */
.tiers {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  list-style: none;
  margin: 2.5rem 0 0;
  padding: 0;
}

.tier {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  display: flex;
  flex-direction: column;
  padding: 1.9rem;
  position: relative;
}

/* The recommendation is a word, set opposite the plan name, and it is
   positioned out of the flow so that the three prices still line up. A badge
   that pushed one card's content down by its own height was the thing making
   this row look crooked. */
.tier.featured { border-color: var(--ink); }

.tier.featured::after {
  color: var(--muted);
  content: "Most shops start here";
  font-size: 0.78rem;
  font-weight: 600;
  position: absolute;
  right: 1.9rem;
  top: 1.9rem;
}

.price { align-items: baseline; display: flex; gap: 0.35rem; margin: 0 0 0.3rem; }
.price .figure { font-size: 2.4rem; }
.price .per { color: var(--faint); font-size: 0.92rem; }

.allowance { color: var(--muted); font-size: 0.9rem; margin: 0 0 1.3rem; }

/* a list that reads as prose: no bullets, a tick and some space */
ul.plain {
  border-top: 1px solid var(--rule);
  flex: 1 1 auto;
  list-style: none;
  margin: 0;
  padding: 0.6rem 0 0;
}

ul.plain li {
  color: var(--muted);
  font-size: 0.885rem;
  padding: 0.45rem 0 0.45rem 1.5rem;
  position: relative;
}

ul.plain li::before {
  color: var(--accent);
  content: "✓";
  font-size: 0.85rem;
  left: 0;
  position: absolute;
  top: 0.45rem;
}

/* pushed to the bottom of the card rather than left under the last feature,
   which is what makes the three of them line up */
.tier .actions { margin-top: auto; padding-top: 1.6rem; }
.tier .actions > *, .tier .actions form, .tier .actions .button { width: 100%; }
.tier .actions button { width: 100%; }

/* ---------- annotated lists ---------- */

/* A list where each item is a claim and a sentence explaining it. */
.notes { list-style: none; margin: 1.5rem 0 0; padding: 0; }

.notes li {
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  font-size: 0.9rem;
  padding: 0.8rem 0;
}

.notes li:first-child { border-top: 1px solid var(--rule); }

.notes strong {
  color: var(--ink);
  font-size: 0.9rem;
  font-weight: 620;
  margin-right: 0.35rem;
}

/* the guide links, as a grid of destinations rather than a column of bullets */
.links {
  display: grid;
  gap: 0 3rem;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
}

.links a {
  border-bottom: 1px solid var(--rule);
  color: var(--ink);
  display: block;
  font-size: 0.92rem;
  padding: 0.85rem 0;
  transition: color 0.16s ease;
}

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

/* ---------- questions ---------- */

/* Objections, answered where somebody has them. Native disclosure, so it
   works with no script and a search engine reads every answer. */
.faq { margin: 1.5rem 0 0; max-width: 50rem; }

.faq details { border-bottom: 1px solid var(--rule); }
.faq details:first-of-type { border-top: 1px solid var(--rule); }

.faq summary {
  align-items: center;
  cursor: pointer;
  display: flex;
  font-size: 1.02rem;
  font-weight: 600;
  gap: 1rem;
  justify-content: space-between;
  letter-spacing: -0.014em;
  list-style: none;
  padding: 1.15rem 0;
}

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

.faq summary::after {
  color: var(--accent);
  content: "+";
  font-size: 1.3rem;
  font-weight: 400;
  line-height: 1;
}

.faq details[open] summary::after { content: "−"; }
.faq summary:hover { color: var(--accent); }

.faq p {
  color: var(--muted);
  font-size: 0.94rem;
  margin: 0 0 1.15rem;
  max-width: 46rem;
}

/* ---------- the closing invitation ---------- */

/* The same invitation the top of the page made, asked once more at the point
   somebody has finished reading. A rule and some space set it apart; it does
   not need a coloured slab to be noticed at the end of a page. */
.cta {
  border-top: 1px solid var(--rule-firm);
  margin-top: var(--sec);
  padding: clamp(2.75rem, 5vw, 4rem) 0 0;
  text-align: center;
}

.cta h2 { margin: 0 auto; max-width: 20ch; }
.cta p { color: var(--muted); margin: 1rem auto 0; max-width: 34rem; }
.cta .actions { justify-content: center; }

/* Running text gets text-wrap: pretty further up, which only tidies the last
   line. Centred under a headline, a short paragraph shows its whole rag, and
   balance is what evens all of it. Left-aligned copy is left alone: there the
   rag is on the side nobody is reading down. */
.hero .standfirst,
.sec-head.centre p,
.cta p { text-wrap: balance; }

/* ---------- panels ---------- */

/* A block of the application's own furniture: the credit meter, a run's
   progress, the result of an audit. */
.panel {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  margin-top: 1.75rem;
  padding: 1.6rem;
}

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

.status {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0 0 1rem;
}

.bar {
  background: color-mix(in srgb, var(--ink) 10%, transparent);
  border-radius: var(--r-pill);
  height: 6px;
  overflow: hidden;
}

.fill {
  background: var(--accent);
  border-radius: var(--r-pill);
  height: 100%;
  transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}

.filters { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 1.75rem 0 1rem; }

.filters a {
  background: var(--sunken);
  border-radius: var(--r-pill);
  color: var(--muted);
  font-size: 0.82rem;
  padding: 0.3rem 0.8rem;
  transition: background-color 0.14s ease, color 0.14s ease;
}

.filters a:hover {
  background: color-mix(in srgb, var(--ink) 7%, var(--sunken));
  color: var(--ink);
  text-decoration: none;
}

.filters a.on { background: var(--accent); color: var(--accent-ink); }
.filters a.on:hover { background: var(--accent-lift); color: var(--accent-ink); }

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

/* Four gradients doing one job, and the one place this stylesheet uses one:
   it is an affordance rather than decoration, and it is invisible until there
   is something off screen. The two paper-coloured covers scroll with the
   content and start out over the two shadows, which are pinned to the box.
   Scroll, and a cover slides off its shadow. */
.scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  background:
    linear-gradient(to right, var(--raise) 35%, transparent) 0 0 / 2.5rem 100% no-repeat local,
    linear-gradient(to left, var(--raise) 35%, transparent) 100% 0 / 2.5rem 100% no-repeat local,
    linear-gradient(to right, color-mix(in srgb, var(--ink) 11%, transparent), transparent)
      0 0 / 1.1rem 100% no-repeat scroll,
    linear-gradient(to left, color-mix(in srgb, var(--ink) 11%, transparent), transparent)
      100% 0 / 1.1rem 100% no-repeat scroll;
}

.items {
  border-collapse: collapse;
  font-size: 0.9rem;
  /* a column of counts only reads as a column when the digits are one width */
  font-variant-numeric: lining-nums tabular-nums;
  width: 100%;
}

.items th {
  border-bottom: 1px solid var(--rule-firm);
  color: var(--faint);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0 0.9rem 0.6rem 0;
  text-align: left;
  white-space: nowrap;
}

.items td {
  border-bottom: 1px solid var(--rule);
  padding: 0.8rem 0.9rem 0.8rem 0;
  vertical-align: top;
}

.items tbody tr:last-child td { border-bottom: 0; }

/* a long run of rows is easier to read across when the row under the cursor is
   picked out. Barely: enough to follow, not enough to notice. */
.items tbody tr:hover td { background: color-mix(in srgb, var(--ink) 3%, transparent); }

.violations { color: var(--bad); margin: 0; padding-left: 1.05rem; }
.violations li { font-size: 0.87rem; }
.violations li::marker { color: color-mix(in srgb, var(--bad) 55%, transparent); }

/* The per-row action in the run panel. Right aligned and narrow, so a column
   of them reads as a margin rather than as another column of data. */
.rowactions { text-align: right; white-space: nowrap; width: 1%; }

/* ---------- the product tracker ---------- */

/* A spreadsheet is not a grid once it is on a screen.

   This page used to draw one table column per column of the uploaded file,
   which for an ordinary marketplace export is twenty of them: a table three
   times wider than the page, with the description set six characters wide.
   Every row was technically on screen and none of it could be read.

   So a row is a record here rather than a line of a grid. The three things
   every row has, whatever the sheet held, are aligned in columns across the
   page: the key, what the row says, and where it has got to. The columns the
   file happened to carry are folded up underneath, and open in place. */

.trackerbar {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 0 2rem;
  justify-content: space-between;
}

.find { align-items: center; display: flex; gap: 0.6rem; margin: 1.6rem 0 0.6rem; }

.find label { color: var(--faint); font-size: 0.82rem; font-weight: 500; margin: 0; }

/* narrower than a form field: this is a control sitting on a page, not a
   question the page is asking */
.find input { font-size: 0.88rem; padding: 0.42rem 0.7rem; width: 17rem; }

/* The hidden attribute loses to any display of our own, and both of these have
   one. Without this the search box shows up on a page whose script never ran,
   and a row the search has just hidden stays exactly where it was. */
.find[hidden],
.record[hidden] { display: none; }

.tally { color: var(--muted); font-size: 0.88rem; margin: 0 0 0.5rem; }

.records {
  /* the one place the three columns are declared, so the head and every
     record are guaranteed to agree */
  --record: minmax(8rem, 13rem) minmax(0, 1fr) minmax(0, auto);

  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  margin-top: 1.2rem;
  padding: 0 1.4rem;
}

.records-head {
  border-bottom: 1px solid var(--rule-firm);
  color: var(--faint);
  display: grid;
  font-size: 0.78rem;
  font-weight: 600;
  gap: 0 1.5rem;
  grid-template-columns: var(--record);
  margin: 0;
  padding: 1.1rem 0 0.7rem;
}

.records-head span:last-child { text-align: right; }

.record {
  border-top: 1px solid var(--rule);
  display: grid;
  gap: 0.15rem 1.5rem;
  grid-template-columns: var(--record);
  margin: 0 -0.75rem;
  padding: 0.95rem 0.75rem;
}

.record:first-of-type { border-top: 0; }
.record:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); border-radius: var(--r-sm); }

.record-key { margin: 0; overflow-wrap: anywhere; }
.record-key a { font-weight: 550; }

.record-says { min-width: 0; }
.record-name { margin: 0; overflow-wrap: anywhere; }

/* Two lines of it, and the rest is one click away. A description copied out of
   a marketplace export runs to a thousand characters, and a list where every
   entry is a page is not a list. */
.record-blurb {
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  color: var(--muted);
  display: -webkit-box;
  font-size: 0.87rem;
  line-clamp: 2;
  margin: 0.2rem 0 0;
  max-width: 48rem;
  overflow: hidden;
}

/* Said where the sheet said nothing. A blank space cannot be told apart from a
   value that failed to render, and this page exists to be believed about
   exactly that. */
.none { color: var(--faint); font-style: italic; }

.record-state {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: flex-end;
  margin: 0;
}

.record-when {
  color: var(--faint);
  font-size: 0.78rem;
  font-variant-numeric: lining-nums tabular-nums;
  white-space: nowrap;
}

/* under what the row says, not under the key, so the eye running down the
   column of keys is not interrupted by it */
.record-row { grid-column: 2 / -1; margin-top: 0.45rem; }

/* One disclosure control, drawn the same in both places this page uses one.
   The platform triangle is replaced rather than lived with: it is a different
   size and colour in every browser and matches none of the type here. */
.record-row > summary,
.columnlist > summary {
  color: var(--faint);
  cursor: pointer;
  display: inline-flex;
  font-size: 0.8rem;
  gap: 0.45rem;
  list-style: none;
  width: fit-content;
}

.record-row > summary::-webkit-details-marker,
.columnlist > summary::-webkit-details-marker { display: none; }

.record-row > summary::before,
.columnlist > summary::before {
  color: var(--accent);
  content: "+";
  font-variant-numeric: tabular-nums;
}

.record-row[open] > summary::before,
.columnlist[open] > summary::before { content: "\2212"; }

.record-row > summary:hover,
.columnlist > summary:hover { color: var(--ink); }

/* The columns of one row, as pairs, set in newspaper columns rather than a
   grid. A grid lines the pairs up in rows, and one thousand-character
   description then leaves a hand-sized hole beside it in every other column.
   The count is a width rather than a number: three columns on a laptop, one on
   a phone, and nothing to keep in step with a breakpoint. */
.cells { column-gap: 1.5rem; columns: 15rem; margin: 0.75rem 0 0.35rem; }

.cells > div {
  border-top: 1px solid var(--rule);
  /* a pair split across two columns reads as two half pairs */
  break-inside: avoid;
  margin-bottom: 0.65rem;
  padding-top: 0.35rem;
}

.cells dt { color: var(--faint); font-size: 0.76rem; }

.cells dd {
  font-size: 0.87rem;
  margin: 0.12rem 0 0;
  /* an image url has no spaces in it to break at, and left alone it decides
     how wide its column is */
  overflow-wrap: anywhere;
}

.columnlist { margin: 0 0 0.4rem; }
.columnlist .tagset { margin-top: 0.7rem; }
.columnlist .tagset span { font-family: var(--mono); font-size: 0.74rem; }
.columnlist .hint { margin-top: 0.6rem; max-width: 40rem; }

.find-empty { color: var(--muted); font-size: 0.9rem; margin: 1.2rem 0 0; }

@media (max-width: 46rem) {
  /* the labels belong to columns that no longer exist */
  .records-head { display: none; }
  .records { padding-top: 0.5rem; }

  .record { gap: 0.3rem; grid-template-columns: 1fr; }

  /* the status reads as part of the key once they are stacked, so it goes
     with it rather than after the description */
  .record-key { order: 1; }
  .record-state { justify-content: flex-start; order: 2; }
  .record-says { order: 3; }
  .record-row { grid-column: 1 / -1; order: 4; }

  .find { width: 100%; }
  .find input { flex: 1; width: auto; }
}

/* ---------- the two scores ---------- */

.versus {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  margin: 1.75rem 0 1.25rem;
}

.scorecard {
  background: var(--raise);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--shadow-1);
  padding: 1.4rem 1.6rem;
}

/* the accent is the only thing marking ours out, and it is the same accent the
   rest of the site uses for "this one" */
.scorecard.ours { border-color: var(--accent); }

.score {
  font-family: var(--display);
  font-size: 3rem;
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: 650;
  letter-spacing: -0.04em;
  line-height: 1;
  margin: 0.4rem 0 0.15rem;
}

.scorecard.ours .score { color: var(--accent); }

.outof { color: var(--faint); font-size: 1.05rem; letter-spacing: 0; }

/* One row per check, the seller's number then ours. */
.compare td { vertical-align: top; }

.compare td .points {
  display: block;
  font-family: var(--display);
  font-size: 1.2rem;
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: 650;
  line-height: 1.15;
}

.compare td .muted { display: block; margin-top: 0.15rem; }

/* Full marks and none at all. Nothing in between is coloured: a scale of five
   shades would make a rubric look more precise than six integers can be. */
.items td.good .points { color: var(--ok); }
.items td.bad .points { color: var(--bad); }

/* ---------- telling somebody the button did something ---------- */

/* A form that posts and then sits there looks broken, and the honest response
   to a page that looks broken is to press the button again. That is the actual
   problem being solved here: not decoration, but the second submission. */

/* Dots rather than a spinning ring, because the reduced-motion block below
   kills every animation with iteration-count 1, which leaves a spinner frozen
   part-way round its arc looking like a broken image. These animate to full
   opacity, so switched off they settle into a plain "..." */
.dots { display: none; margin-left: 0.1rem; vertical-align: baseline; }

.dots i {
  animation: dot 1.05s ease-in-out infinite;
  background: currentColor;
  border-radius: 50%;
  display: inline-block;
  height: 0.32em;
  margin-left: 0.18em;
  width: 0.32em;
}

.dots i:nth-child(2) { animation-delay: 0.16s; }
.dots i:nth-child(3) { animation-delay: 0.32s; }

/* Ending at full opacity is deliberate, so the reduced-motion snap lands on
   three visible dots rather than three invisible ones. */
@keyframes dot {
  0%   { opacity: 0.25; }
  50%  { opacity: 1; }
  100% { opacity: 1; }
}

/* Busy, by either route: htmx puts .htmx-request on the element that made the
   request, and the small script for ordinary forms sets aria-busy on the
   button it just locked. */
button[aria-busy="true"] .dots,
.htmx-request .dots { display: inline-block; }

/* A locked button has to look locked, or somebody reads an unresponsive
   control as a page that has stopped working. Not-allowed rather than a faded
   button: the fade says "this is off", and this one is not off, it is busy. */
.button:disabled,
.button[aria-busy="true"],
button:disabled { cursor: not-allowed; filter: saturate(0.6); }

/* The panel polls itself every two seconds, so a spinner that appeared on
   every poll would flash forever. Only the buttons say anything. */
#panel.htmx-request .dots { display: none; }

/* ---------- the typed-in product grid ---------- */

/* The grid is a table, not prose, so its form is not held to the reading
   measure every other form in a .prose section is. Eight columns squeezed into
   a paragraph's width is a page you can only read through a letterbox. The
   parts of the form that are prose keep the measure for themselves, so the
   sentences and the two pickers stay the width they should be. */
.prose > form.grid-form { max-width: none; }

.prose > form.grid-form > p,
.prose > form.grid-form > ul,
.prose > form.grid-form > label,
.prose > form.grid-form > select { max-width: var(--measure); }

/* A table of inputs, which is a table first and a form second. The cells are
   tightened right down: the ordinary input padding is sized for a form with
   six fields in a column, and a grid of forty boxes with it is a page nobody
   can see the shape of. */
.items.grid th,
.items.grid td {
  padding: 0.3rem 0.35rem 0.3rem 0;
  vertical-align: middle;
}

.items.grid input[type="text"] {
  font-size: 0.87rem;
  min-width: 8.5rem;
  padding: 0.42rem 0.55rem;
}

/* The header row is the one place a seller renames a column, so it reads as
   editable rather than as a label that happens to sit in a box. */
.items.grid thead input[type="text"] {
  background: transparent;
  border-color: var(--rule);
  font-size: 0.8rem;
}

.items.grid thead input[type="text"]:focus-visible { background: var(--raise); }

/* The row numbers are a margin rather than a column of data, so they take as
   little width as they can. */
.items.grid .rownum {
  padding-right: 0.6rem;
  text-align: right;
  width: 1.5rem;
}

/* The row highlight is for reading a long table. Here every row is a set of
   boxes somebody is typing into, and the focus ring is the thing that should
   say where they are. */
.items.grid tbody tr:hover td { background: none; }

/* The copy button that sits inside a field's own label. It is a word next to a
   heading rather than a control, so it stays out of the way until the pointer
   is on it. */
.eyebrow .plain.small {
  margin-left: 0.5rem;
  opacity: 0.65;
}

.eyebrow .plain.small:hover { opacity: 1; }

/* ---------- when movement is unwelcome ---------- */

/* Someone who gets motion sick from a progress bar sliding across the screen
   has said so in their system settings. The bar still fills; it just arrives
   there instead of travelling. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- on paper ---------- */

/* A run page is a thing people print or save to PDF to show somebody, so it
   ought to survive the trip. Ink on white, no navigation, and every link in
   the prose carries its address, because a printed page cannot be clicked. */
@media print {
  :root {
    --paper: #fff;
    --raise: #fff;
    --sunken: #fff;
    --ink: #000;
    --muted: #333;
    --faint: #555;
    --rule: #bbb;
    --rule-firm: #999;
    --accent: #000;
  }

  body { background: #fff; color: #000; font-size: 11pt; }

  header, footer, .actions, .filters, .skip, .announce, .cta { display: none; }

  main { max-width: none; padding: 0; }

  .panel, .prose, .fineprint, .card, .split > *, .records, .tier, .scorecard {
    border: 1px solid #bbb;
    box-shadow: none;
    break-inside: avoid;
  }

  h1, h2 { break-after: avoid; }
  .items tr { break-inside: avoid; }
  .record { break-inside: avoid; }

  .scroll { background: none; overflow: visible; }

  a { color: #000; text-decoration: underline; }

  .prose a[href^="http"]::after,
  .prose a[href^="/"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    word-break: break-all;
  }
}
