/*
 * Sift workspace shell
 * A mail-client layout: a collapsible sidebar rail and one white working
 * surface. Mobile first; the sidebar becomes a drawer under 960px, which is
 * the only width where the topbar (the drawer opener alone) is shown.
 */

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');

/*
 * The design tokens, and the ONLY place a raw value is written. A component names a role
 * (`--theme-foreground-low-contrast`), never a colour; a size (`--size-2x`), never a pixel.
 *
 * Spacing is an 8px grid: half 4, 1x 8, 2x 16 ... 8x 64. `--size-inset` is the one legal value
 * off that grid, 12px, for the inner padding of a list, a table cell and a divider, which is
 * the exception the reference system makes for itself.
 *
 * Type is a fixed ladder rather than fluid sizes; 40 is 17px and is what a card or a section
 * title is set in. Headings are LIGHT: 500, and 400 once they are small. Tracking is 0
 * everywhere, so this file declares no letter-spacing at all - a negative one is a bug here.
 */
:root {
  --size-half: 4px;
  --size-1x: 8px;
  --size-inset: 12px;
  --size-2x: 16px;
  --size-3x: 24px;
  --size-4x: 32px;
  --size-5x: 40px;
  --size-6x: 48px;
  --size-7x: 56px;
  --size-8x: 64px;

  --font-size-10: 12px;
  --font-size-20: 13px;
  --font-size-30: 14px;
  --font-size-40: 17px;
  --font-size-50: 20px;
  --font-size-60: 24px;
  --font-size-70: 32px;

  --font-weight-regular: 400;
  --font-weight-medium: 500;

  /* The page ground. It is WHITE and not an off-white: a slightly grey ground behind white
     regions draws a seam wherever a region stops, which on a short page is a grey band under the
     content and on a capped one a grey column beside it. One ground, one colour, no seam. */
  --theme-background-base: #ffffff;
  --theme-background-container: #ffffff;
  --theme-background-container-alt: #f4f5f7;
  --theme-background-inverse: #111418;
  --theme-background-accent: #3e69ff;
  --theme-background-accent-hover: #2a52dd;
  --theme-background-accent-subtle: #eff3ff;
  --theme-background-accent-muted: #dce4ff;
  --theme-background-success-subtle: #e8f5ec;
  --theme-background-caution-subtle: #fdf6e7;
  --theme-background-error-subtle: #fdeeee;

  --theme-foreground-high-contrast: #111418;
  --theme-foreground-medium-contrast: #5b6472;
  --theme-foreground-low-contrast: #8b95a3;
  --theme-foreground-inverse: #ffffff;
  --theme-foreground-success: #15803d;
  --theme-foreground-caution: #b45309;
  --theme-foreground-error: #dc2626;
  --theme-accent: #3e69ff;

  --theme-border-low-contrast: #e3e6ea;
  --theme-border-high-contrast: #cfd4db;

  --shadow-sm: none;
  --shadow-md: 0 2px 6px rgba(17, 20, 24, 0.07);
  --shadow-lg: 0 10px 30px rgba(17, 20, 24, 0.12);

  --radius-sm: 4px;
  --radius: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;

  --topbar-height: 56px;
  --sidebar-width: 240px;
  --sidebar-rail: 64px;
  /* A form is capped narrower than the page it sits on: past about this width the eye loses the
     line between a label and the control under it. */
  --form-max-width: 912px;
  --field-width: 40rem;

  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: 'JetBrains Mono', 'SFMono-Regular', ui-monospace, monospace;

  /* One curve, and three durations that say what is moving: opacity 0.2s, colour and
     background 0.3s, transform 0.5s. */
  --bezier: cubic-bezier(0.5, 0, 0, 0.75);
  --duration-opacity: 0.2s;
  --duration-colour: 0.3s;
  --duration-transform: 0.5s;
}


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

html {
  color-scheme: light;
}

body {
  margin: 0;
  min-height: 100dvh;
  background: var(--theme-background-base);
  color: var(--theme-foreground-high-contrast);
  font-family: var(--font-sans);
  font-size: var(--font-size-30);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a {
  color: var(--theme-accent);
  text-decoration: none;
}

a:hover {
  color: var(--theme-background-accent-hover);
}

/*
 * One focus ring for everything that is not a field. Somebody navigating by keyboard has this
 * and nothing else to say where they are, so it is declared once, for every interactive role at
 * once, rather than per component: a component that forgets it is then a component that has to
 * opt out. Fields set their own ring at offset 0 in `.form-control`, because an inset ring reads
 * as part of the box a value is typed into.
 */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
.btn:focus-visible,
.action-link:focus-visible,
.action-button:focus-visible,
.tab:focus-visible,
.side-link:focus-visible,
.icon-btn:focus-visible,
.mode-card:focus-visible,
.plan-editor__tab:focus-visible,
.pagy a:focus-visible {
  outline: 2px solid var(--theme-accent);
  outline-offset: 2px;
}

button,
input,
select,
textarea {
  font: inherit;
}

code,
pre,
.cell-code,
.mono {
  font-family: var(--font-mono);
}

.metric-value,
.stat-value {
  font-variant-numeric: tabular-nums;
}

.icon {
  width: 1.05rem;
  height: 1.05rem;
  flex: 0 0 auto;
}

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

.shell {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-height: 100dvh;
}

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

/* The content area is a SURFACE, not a card: the rail's right edge and the topbar's bottom edge
   are what separate it, so it carries no border, no inset margin and no corner of its own. It was
   a 12px-rounded bordered panel floating 8px inside the window, which is the one shape the
   reference system never draws - there every region is bounded by a hairline and the only rounded
   things are the controls and the widgets inside them. What the margin was doing for breathing
   room the page gutter does instead. */
.app-shell .main-content {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  /* No cap. The page takes the window, flush against the rail on one side and the window edge on
     the other, which is what every list on it wants: a table of eleven columns spends every pixel
     it is given and a capped one wrapped its cells while the screen sat empty beside it. What is
     still capped is a FORM (`--form-max-width`, 912px), because there the line length between a
     label and its control is the whole point. */
  padding: var(--size-3x) var(--size-3x) var(--size-6x);
  background: var(--theme-background-container);
}

/* -------------------------------------------------------------- sidebar -- */

/* No right edge and no bottom edge on the topbar either: the chrome is one white field with the
   navigation on the left and the page on the right, and nothing is drawn between them. One
   consequence worth knowing rather than discovering: the topbar is sticky, so scrolled content
   now passes under it with no line to meet, which is the trade this makes deliberately. */
.sidebar {
  position: fixed;
  z-index: 40;
  top: 0;
  bottom: 0;
  left: 0;
  width: min(17.5rem, 86vw);
  display: flex;
  flex-direction: column;
  gap: var(--size-half);
  padding: var(--size-1x) var(--size-1x) var(--size-inset);
  background: var(--theme-background-container);
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  scrollbar-width: none;
  transform: translateX(-102%);
  transition: transform var(--duration-transform) var(--bezier);
}

body[data-drawer="open"] .sidebar {
  transform: translateX(0);
  box-shadow: var(--shadow-lg);
}

.sidebar::-webkit-scrollbar {
  display: none;
}

.shell__scrim {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: none;
  padding: 0;
  border: 0;
  background: rgba(17, 20, 24, 0.32);
  backdrop-filter: blur(var(--size-2x));
  cursor: pointer;
}

body[data-drawer="open"] .shell__scrim {
  display: block;
}

.sidebar__head {
  display: flex;
  align-items: center;
  gap: var(--size-half);
  min-height: 2.75rem;
  padding: 0 var(--size-half);
  margin-bottom: var(--size-1x);
}

.sidebar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  min-width: 0;
  color: var(--theme-foreground-high-contrast);
}

.sidebar__brand:hover {
  color: var(--theme-foreground-high-contrast);
}

.sidebar__brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  background: var(--theme-accent);
  color: var(--theme-foreground-inverse);
}

.sidebar__brand-name {
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size-4x);
  height: var(--size-4x);
  flex: 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--theme-foreground-medium-contrast);
  cursor: pointer;
  transition: background var(--duration-colour) var(--bezier), color var(--duration-colour) var(--bezier);
}

.icon-btn:hover,
.icon-btn:focus-visible {
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
}

.sidebar__nav {
  display: grid;
  gap: var(--size-half);
}

/* A row of the rail, not a pill in it. The rail is a REGION and its rows run its full width, so
   the hover and the selection are square bands sitting flush against both its edges; a rounded
   fill inset from them read as a floating chip and gave the one region on screen a corner nothing
   else in the chrome has. The negative inline margin is what takes the band out to those edges
   while the words stay on the rail's own gutter. */
.side-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  min-height: var(--size-4x);
  margin-inline: calc(var(--size-1x) * -1);
  padding: 0 var(--size-2x);
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  transition: background var(--duration-colour) var(--bezier), color var(--duration-colour) var(--bezier);
}

.side-link:hover,
.side-link:focus-visible {
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
}

.side-link.is-active {
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

.side-link__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
}

.side-link__label {
  overflow: hidden;
  text-overflow: ellipsis;
}

.side-link__badge {
  margin-left: auto;
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--theme-foreground-low-contrast);
}

.side-link__badge--theme-foreground-error {
  color: var(--theme-foreground-error);
}

.sidebar__foot {
  display: grid;
  gap: var(--size-half);
  margin-top: auto;
  padding-top: var(--size-1x);
}

/* Who you are, at the foot of the rail. A hairline above it because this is the one thing down
   here that is not a section of the app, and the rail has no other rule on it to confuse it with.
   It is drawn to the rail's edges the way a side-link is, so the three sit on one left edge. */
.sidebar__account {
  display: flex;
  align-items: center;
  gap: var(--size-half);
  margin: var(--size-1x) calc(var(--size-1x) * -1) 0;
  padding: var(--size-1x) var(--size-2x) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

/* Pushed to the end of the row so the logout sits on the rail's right edge, with the language
   between the two. */
.sidebar__account-lang {
  margin-left: auto;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
}

.account-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  min-width: 0;
  flex: 0 0 auto;
  padding: var(--size-half) var(--size-half);
  border-radius: var(--radius-sm);
}

.account-chip__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

.account-chip__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.account-chip__form {
  margin: 0;
  flex: 0 0 auto;
}

/* --------------------------------------------------------------- topbar -- */

/*
 * A thin context bar: which workspace and section you are in on the left, the narrowing controls
 * in the middle, and the account and utilities on the right. The account moved here out of the
 * foot of the rail, because the rail is navigation and who you are signed in as is not a section
 * of the app.
 */
/* 56px because the bar carries a 40px control now, which is also the height the reference gives
   its own top bar. The horizontal inset matches the page's gutter under it, so the search box and
   the page title below it stand on nothing in common - the bar's controls belong to the bar. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  min-height: var(--topbar-height);
  gap: var(--size-3x);
  padding: var(--size-1x) var(--size-3x);
  background: var(--theme-background-container);
}

/* In the bar the filter row owns no vertical space of its own, and it is pushed to the far side
   of the title: the title says what the screen is and sits on the page's left edge, the
   narrowings and the screen's actions gather at the other end. */
.topbar .filter-bar {
  flex: 0 1 auto;
  min-width: 0;
  margin: 0 0 0 auto;
}

/* Whatever the screen offers, at the end of the bar. It follows the filters, so a screen with
   both reads left to right as: what this is, what it is narrowed to, what you can do. With no
   filters the actions take the auto margin themselves and still land at the end. */
.topbar__actions,
.page-header__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-1x);
  flex: 0 0 auto;
  margin-left: auto;
}

/* Two auto margins in one row would split the space between them, so the actions give theirs up
   when a filter row is already holding the title apart from them. */
.topbar .filter-bar ~ .page-header__actions,
.topbar .filter-bar ~ .topbar__actions {
  margin-left: 0;
}





/* On a phone the bar wraps into rows rather than squeezing one, and it STOPS BEING STICKY. Pinned,
   the four things it carries stacked to about 200px and held a third of a 700px screen for the
   whole scroll; a phone has no width to lay them out side by side and no height to spare for them,
   so here the head of the screen scrolls away like the screen. The drawer opener is what stays
   reachable, and it is in the rail's own drawer. */
@media (max-width: 899px) {
  .topbar {
    position: static;
    flex-wrap: wrap;
    gap: var(--size-1x) var(--size-2x);
    padding-inline: var(--size-2x);
  }

  /* The narrowings take a row of their own, below the title and its action. */
  .topbar .filter-bar,
  .topbar .tabs {
    flex: 1 1 100%;
    order: 1;
  }

  .topbar .filter-bar {
    margin-left: 0;
  }

  .page-title {
    font-size: var(--font-size-50);
  }
}

/* Below the desktop tier the rail is a drawer, padded the way the reference pads its compact
   navigation: 16px 6px 16px 12px. The 6px is its number, not this grid's. */
@media (max-width: 1199px) {
  .sidebar {
    padding: var(--size-2x) 6px var(--size-2x) var(--size-inset);
  }
}

@media (min-width: 1200px) {
  .topbar__menu {
    display: none;
  }

  .shell {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .sidebar {
    position: sticky;
    top: 0;
    left: auto;
    height: 100dvh;
    width: var(--sidebar-width);
    transform: none;
    transition: width var(--duration-colour) var(--bezier);
  }

  .shell__scrim {
    display: none !important;
  }

  body[data-sidebar="collapsed"] .sidebar {
    width: var(--sidebar-rail);
    padding-inline: var(--size-inset);
  }

  body[data-sidebar="collapsed"] .sidebar__label {
    display: none;
  }

  body[data-sidebar="collapsed"] .sidebar__head {
    justify-content: center;
  }

  body[data-sidebar="collapsed"] .sidebar__brand {
    display: none;
  }

  /* Still a full-width band, with the icon centred in it rather than in a tile of its own: the
     rail narrows, the row does not stop being a row of it. */
  body[data-sidebar="collapsed"] .side-link {
    justify-content: center;
    min-height: 2.6rem;
    margin-inline: calc(var(--size-inset) * -1);
    padding-inline: 0;
  }

  body[data-sidebar="collapsed"] .sidebar__nav {
    gap: var(--size-half);
  }

  /* Stacked, not squeezed: the row is an avatar, a name, a language and a logout, and three of
     those four go when the rail narrows to 64px. What is left is put in a column so the logout
     keeps a hit area instead of being pushed off the rail's right edge. */
  body[data-sidebar="collapsed"] .sidebar__account {
    flex-direction: column;
    gap: var(--size-1x);
    margin-inline: calc(var(--size-inset) * -1);
    padding-inline: 0;
  }

  body[data-sidebar="collapsed"] .sidebar__account-lang {
    display: none;
  }

  body[data-sidebar="collapsed"] .side-link__badge {
    position: absolute;
    top: 0.1rem;
    right: 0.05rem;
    margin: 0;
    min-width: 1rem;
    padding: 0 var(--size-half);
    border-radius: var(--radius-sm);
    background: var(--theme-background-container-alt);
    color: var(--theme-foreground-low-contrast);
    font-size: var(--font-size-10);
    line-height: 1rem;
    text-align: center;
  }

  /* The rule above anchors EVERY collapsed badge to one point, which was written when a link
     carried at most one. The Sources link carries two (how many sources you have, and how many
     of them stopped fetching), and both were drawn into that same rectangle, so the later one in
     the DOM - this one, which has a background - hid the count entirely. Sending it to the other
     end of the link's own 2.6rem band is the smallest thing that separates them: it needs no new
     selector, and every other link, the Background Jobs one included, still has one badge in the
     top-right corner it always had. */
  body[data-sidebar="collapsed"] .side-link__badge--theme-foreground-error {
    top: auto;
    bottom: 0.1rem;
    background: var(--theme-background-error-subtle);
    color: var(--theme-foreground-error);
  }

  .app-shell .main-content {
    padding: var(--size-2x) var(--size-2x) var(--size-5x);
  }

  .topbar {
    padding: var(--size-1x) var(--size-2x);
  }
}

/*
 * The filter bar of a list, rendered under that list's title rather than in the application
 * chrome. Every control in it is an ordinary field - a label above a bordered box - so the bar
 * reads as the same kind of thing as the forms elsewhere in the app.
 *
 * The row is a LINE of controls, so it is spaced like one: 24px between two filters, which is the
 * field step of the ladder, and 16px between two rows once it wraps.
 */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-2x) var(--size-3x);
  margin: 0 0 var(--size-3x);
}

/* The search is the one CONTROL on the line, so it is the one thing wearing a box: the ordinary
   40px field with the ordinary hairline, capped well under the form measure. Left to grow the box
   swallowed every pixel the row had spare and flung the narrowings beside it to the far side of
   the screen; the leftover width belongs at the END of the line, where empty space is not a
   problem to fix. */
.filter-bar__search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 12rem;
  min-width: 0;
  max-width: 20rem;
}

/* The glyph sits on the box's own inner inset and takes no pointer, so a click over it lands in
   the field. It is the quiet ink, because it names the field rather than being the field. */
.filter-bar__search-icon {
  position: absolute;
  left: var(--size-inset);
  display: inline-flex;
  color: var(--theme-foreground-low-contrast);
  pointer-events: none;
}

.filter-bar__search-icon .icon {
  width: 1rem;
  height: 1rem;
}

/* Room for the glyph, and nothing else different about it. Written as a descendant so it beats
   `.form-control`'s own `padding` shorthand, which sits later in this file and would otherwise
   reset the inset back to 12px and draw the glyph over the first letter. */
.filter-bar__search .form-control {
  padding-left: var(--size-5x);
}

/* A stated narrowing rather than a boxed one: the name in the quiet ink, the chosen value in the
   darkest one, and a chevron after it. This is the row the reference draws under a page title -
   `Date Range Jul 3, 2026 - Jul 20, 2026` - and it is what tells a search apart from a narrowing
   without either of them having to be labelled as such. */
.filter-bar__stat {
  display: flex;
  align-items: baseline;
  gap: var(--size-1x);
  flex: 0 0 auto;
  min-width: 0;
}

.filter-bar__stat-label {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-regular);
  white-space: nowrap;
}

/* The value as it is DRAWN: the span holds the width and the chevron, and the select over it holds
   the behaviour. See the partial for why the two are split. */
.filter-bar__stat-control {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-width: 0;
}

.filter-bar__stat-shown {
  padding-right: var(--size-2x);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' fill='none' stroke='%238b95a3' stroke-width='1.4'%3E%3Cpath d='M2 4l3 3 3-3'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  transition: color var(--duration-colour) var(--bezier);
}

/* Over the whole of the span and invisible: it is still the control that is clicked, focused and
   read out, so nothing about it is hidden from anybody - only its painting is. */
.filter-bar__stat-select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  opacity: 0;
  cursor: pointer;
}

.filter-bar__stat-control:hover .filter-bar__stat-shown {
  color: var(--theme-accent);
}

/* The ring goes round the value and its chevron with a little air, because the control has no box
   of its own for one to sit on. */
.filter-bar__stat-select:focus-visible + .filter-bar__stat-shown,
.filter-bar__stat-control:has(.filter-bar__stat-select:focus-visible) {
  outline: 2px solid var(--theme-accent);
  outline-offset: var(--size-half);
}

.filter-bar__actions {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
}

/* ---------------------------------------------------------------- flash -- */

.flash {
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  padding: var(--size-1x) var(--size-inset);
  margin: 0 0 var(--size-2x);
  border-radius: var(--radius);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container-alt);
  font-weight: var(--font-weight-medium);
}

.flash__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-sm);
  flex: 0 0 auto;
}

.flash-notice {
  border-color: var(--theme-border-low-contrast);
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
}

.flash-notice .flash__icon {
  background: var(--theme-background-success-subtle);
  color: var(--theme-foreground-success);
}

.flash-alert {
  border-color: var(--theme-border-low-contrast);
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
}

.flash-alert .flash__icon {
  background: var(--theme-background-error-subtle);
  color: var(--theme-foreground-error);
}


.page-eyebrow {
  margin: 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

/* In the bar now rather than at the top of the page, at the SAME 24px it has always been: the row
   is 56px and a 24px heading occupies 29 of them, so it fits beside the 40px controls without the
   bar growing. It does not wrap - a screen name is one line - and it never shrinks to make room. */
.page-title {
  margin: 0;
  flex: 0 0 auto;
  font-size: var(--font-size-60);
  line-height: 1.2;
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.page-description,
.section-description {
  margin: 0;
  max-width: 65ch;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
}

.header-actions,
.inline-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-inset);
}

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-1x);
  align-items: center;
}

.cluster--wide {
  gap: var(--size-inset);
}

.stack {
  display: grid;
  gap: var(--size-2x);
}

.stack-sm {
  display: grid;
  gap: var(--size-1x);
}

.stack-lg {
  display: grid;
  gap: var(--size-3x);
}

/* A FRAME is for something that is set apart from the page: a callout, a readout, an empty state,
   a login form on an otherwise blank screen. It is NOT for a region of the page itself. A table, a
   chart and a form are the page's content, so they are drawn on the page rather than in a box on
   it: the section heading and its hairline say where each one starts, and the table's own row
   rules say where its rows are. The reference system draws no wrapper round any of the three.
   Framing them boxed the content inside a second left edge a few pixels in from the title above
   it, so nothing on the screen shared one vertical line.

   A CHART is on the framed side of that line, with the counter cards it sits under: it is a
   widget, a picture of one measure with its own title, and not a region of the page. Read against
   the table below it the difference is the point - the table IS the page's content and the chart
   is a thing on the page. */
.panel,
.chart-card,
.metric-card,
.login-shell {
  background: var(--theme-background-container);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  box-shadow: none;
  backdrop-filter: none;
}

.panel,
.chart-card {
  padding: var(--size-2x);
}

.panel-muted {
  background: var(--theme-background-container-alt);
}

.panel-danger {
  background: var(--theme-background-error-subtle);
  border-color: var(--theme-border-low-contrast);
}

.panel-warning {
  background: var(--theme-background-caution-subtle);
  border-color: var(--theme-border-low-contrast);
}

.panel-code {
  padding: var(--size-2x);
  overflow: auto;
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  box-shadow: none;
}

.panel-code pre,
.panel-code code {
  color: inherit;
  margin: 0;
}

.section-card {
  padding: var(--size-2x);
  border-top: 1px solid var(--theme-border-low-contrast);
}

.section-card:first-child {
  border-top: none;
}

.section-title {
  margin: 0 0 var(--size-1x);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.section-subtitle {
  margin: var(--size-2x) 0 var(--size-1x);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

/* A button is a control, so it is sized like one: the same 40px height and the same radius as the
   field above it, at the interface body size. `.btn-sm` and `.btn-icon` below are the table-row
   sizes and stay smaller, because a row carrying six of them cannot spend 40px on each. */
.btn,
.action-link,
.action-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--size-1x);
  min-height: var(--size-5x);
  padding: 0 var(--size-3x);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--duration-transform) var(--bezier), background var(--duration-colour) var(--bezier), color var(--duration-colour) var(--bezier), border-color var(--duration-colour) var(--bezier), box-shadow var(--duration-colour) var(--bezier);
}

.btn:active,
.action-link:active,
.action-button:active {
  transform: none;
}

.btn-primary {
  background: var(--theme-accent);
  color: var(--theme-foreground-inverse);
  box-shadow: none;
}

.btn-primary:hover,
.btn-primary:focus-visible {
  color: var(--theme-foreground-inverse);
  background: var(--theme-background-accent-hover);
  box-shadow: none;
}

/* The quiet half of a pair: an outline and nothing else. A second filled button beside the
   primary one leaves nobody able to tell which action the screen is for. */
.btn-secondary,
.action-link,
.action-button {
  background: transparent;
  border-color: var(--theme-border-low-contrast);
  color: var(--theme-foreground-high-contrast);
}

.btn-secondary:hover,
.btn-secondary:focus-visible,
.action-link:hover,
.action-link:focus-visible,
.action-button:hover,
.action-button:focus-visible {
  color: var(--theme-foreground-high-contrast);
  border-color: var(--theme-border-high-contrast);
  background: var(--theme-background-container-alt);
}

.btn-danger {
  background: transparent;
  border-color: var(--theme-border-low-contrast);
  color: var(--theme-foreground-error);
}

.btn-danger:hover,
.btn-danger:focus-visible {
  background: var(--theme-background-error-subtle);
  color: var(--theme-foreground-error);
}

.btn-sm {
  min-height: var(--size-4x);
  padding: var(--size-half) var(--size-1x);
  font-size: var(--font-size-10);
}

/* An action that is one icon: the label is its accessible name and its tooltip, which is what
   lets a table carry six of them per row without spending a third of the width on repeated
   words. */
.btn-icon {
  min-width: 1.7rem;
  padding-inline: var(--size-half);
}

.btn-compact {
  min-height: var(--size-5x);
  padding-inline: var(--size-inset);
}

.action-form {
  display: inline-flex;
  margin: 0;
}

.action-cell {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.action-cell--table .btn span {
  white-space: nowrap;
}

.tabs {
  display: flex;
  gap: var(--size-half);
  overflow-x: auto;
  padding: var(--size-half);
  margin: 0 0 var(--size-2x);
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
  scrollbar-width: none;
}

.tabs::-webkit-scrollbar {
  display: none;
}

/* In the sticky bar with the search, because it is the same kind of thing: a narrowing of the
   list. It started centred under the title, belonging to neither the row above it nor the table
   below, and the two steps since - right-aligned on the page, then into the bar - were both about
   putting it beside the other narrowings rather than adrift from them. */
.tabs--feed-filters {
  width: fit-content;
  max-width: 100%;
}

/* In the bar it follows the search box, and the pair takes the auto margin as one group. Its own
   bottom margin is a page one and has no place in a row. */
.topbar .tabs {
  margin: 0;
  flex: 0 0 auto;
}

.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 1.95rem;
  padding: var(--size-half) var(--size-inset);
  border-radius: var(--radius-sm);
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  transition: background var(--duration-colour) var(--bezier), color var(--duration-colour) var(--bezier);
}

.tab:hover,
.tab:focus-visible {
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
}

.tab.active {
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
}

/* The head of one tab panel: what the panel holds, and the one action that adds another of them.
   A flex row rather than a heading with a button under it, so the action sits on the title's own
   line instead of pushing the table further down every tab. */
.tab-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-inset);
}

.tab-head .section-title {
  margin: 0;
}

/*
 * The source form: three numbered cards in the main column and a rail beside them that says
 * what the form AS IT STANDS will make the source do. The save action lives in the header
 * rather than at the foot, because the foot of this form is a screen and a half down and a
 * person correcting one field should not have to travel to it.
 *
 * It replaced a nine-section stack, a 1-2-3 "setup guide" band that named no field, and four
 * large source-type cards; the rules those needed (.setup-guide, .setup-step, .source-basics-grid,
 * .source-type-picker, .source-refresh-note, .form-step-block) went with the markup.
 */

.feed-form__header {
  margin: 0 0 var(--size-5x);
  padding: 0;
  background: none;
  border-bottom: 0;
}

.feed-form__heading {
  display: grid;
  gap: var(--size-half);
  min-width: 0;
}

.feed-form__crumbs {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  margin: 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-regular);
}

.feed-form__crumb-sep {
  width: 0.85rem;
  height: 0.85rem;
  color: var(--theme-border-high-contrast);
}

.feed-form__crumb-current {
  color: var(--theme-accent);
}

.feed-form__title {
  margin: 0;
  font-size: var(--font-size-70);
  font-weight: var(--font-weight-medium);
}

.feed-form__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.feed-form__layout {
  display: grid;
  gap: var(--size-2x);
  align-items: start;
}

.feed-form__main,
.feed-form__rail {
  display: grid;
  gap: var(--size-2x);
  min-width: 0;
  align-content: start;
}

/* The column of sections is capped and spaced by the section step of the ladder; the rail beside
   it is outside that cap, because it is a readout rather than something anybody fills in. */
.feed-form__main {
  max-width: var(--form-max-width);
  gap: var(--size-5x);
}

.form-step {
  padding: 0;
  border: 0;
  background: none;
}

/* Everything stacked inside a step is a field or a group of fields, so the gap between them is
   the field step, 24px, not the 16px this used to be. */
.form-step > * + * {
  margin-top: var(--size-3x);
}

.form-step--quiet {
  padding: var(--size-1x) var(--size-inset);
}

/* The head of a step is that section's legend, so it takes the legend's rule: 12px under the
   words, then the hairline, and everything below it belongs to this heading. */
.form-step__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--size-1x);
  padding-bottom: var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.form-step__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--theme-background-accent-subtle);
  color: var(--theme-accent);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

/* Inline rather than block, which changes nothing while the head row is a flex container - a flex
   item is blockified either way - and keeps the number, the title and the lede on one line in an
   engine that refuses `display: flex` on the <legend> this row now is. */
.form-step__title {
  display: inline;
  margin: 0;
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.form-step__lede {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-20);
}

/* One column at the one width: a pair split inside it would give each half 19rem, narrower than
   anything on these forms needs, and would break the single left-to-right edge the form is read
   down. */
.field-grid {
  display: grid;
  gap: var(--size-3x);
  align-items: start;
  max-width: var(--field-width);
}

/*
 * The shared textarea floor is 8rem, which is right for a form whose only textarea is a prompt
 * and wrong for this one: a two-row headers box and a six-row query would both be drawn the same
 * height and the row beside them would be pushed to the bottom of its cell. Here the `rows` a
 * field declares is what decides its height.
 */
.feed-form textarea.form-control {
  min-height: 0;
}

.mode-guide {
  display: grid;
  gap: var(--size-1x);
}

.mode-card {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  width: 100%;
  padding: var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
  text-align: left;
  cursor: pointer;
  transition: border-color var(--duration-colour) var(--bezier), background var(--duration-colour) var(--bezier), box-shadow var(--duration-colour) var(--bezier);
}

.mode-card:hover,
.mode-card:focus-visible {
  border-color: var(--theme-border-high-contrast);
  background: var(--theme-background-container-alt);
}

.mode-card:active {
  transform: scale(0.985);
}

.mode-card.active {
  border-color: var(--theme-accent);
  background: var(--theme-background-accent-subtle);
  box-shadow: none;
}

.mode-card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  flex: 0 0 auto;
  border-radius: var(--radius-sm);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
}

.mode-card.active .mode-card__icon {
  background: var(--theme-background-container);
  color: var(--theme-accent);
}

.mode-card__body {
  display: grid;
  gap: var(--size-half);
  min-width: 0;
}

.mode-card__title {
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.mode-card__pair {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.pair-row {
  display: grid;
  gap: var(--size-inset);
  align-items: end;
  max-width: var(--field-width);
}

.pair-row__hint {
  color: var(--theme-foreground-low-contrast);
  font-weight: var(--font-weight-medium);
}

.pair-row__arrow {
  display: none;
  align-items: center;
  justify-content: center;
  height: 2.7rem;
  color: var(--theme-foreground-low-contrast);
}

/* ONE treatment for an explanation, wherever it appears: no fill, no tint, a rule down the side
   and the same 12px the helper text under a field uses. The sentence under the transport pair and
   the "selectors are a plan" callout are the same thing and now look like it. */
.pair-sentence,
.form-callout {
  display: grid;
  gap: var(--size-half);
  margin: 0;
  padding: 0 0 0 var(--size-inset);
  border: 0;
  border-left: 2px solid var(--theme-border-low-contrast);
  border-radius: 0;
  background: none;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  line-height: 1.4;
}

.request-block {
  margin-top: var(--size-5x);
  padding-top: 0;
  border-top: 0;
}

.request-block > * + * {
  margin-top: var(--size-3x);
}

.request-block__head {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
}

.request-block__head .section-subtitle {
  margin: 0;
}

.request-block__rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--theme-border-low-contrast);
}

/*
 * The "advanced" disclosure. Nothing inside it is ever disabled: a closed block still submits
 * the token or the operation name it holds, so collapsing a field is a decision about reading
 * the form and not about what the form saves.
 */
.advanced-block {
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.advanced-block__summary {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-inset);
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  list-style: none;
}

.advanced-block__summary::-webkit-details-marker {
  display: none;
}

.advanced-block__caret {
  width: 0.9rem;
  height: 0.9rem;
  transition: transform var(--duration-transform) var(--bezier);
}

.advanced-block[open] .advanced-block__caret {
  transform: rotate(90deg);
}

.advanced-block__body {
  display: grid;
  gap: var(--size-2x);
  padding: 0 var(--size-inset) var(--size-inset);
}

.refresh-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--size-1x);
}

.refresh-row__every {
  display: flex;
  align-items: center;
  gap: var(--size-half);
  padding: 0 var(--size-inset);
  border: 1px solid var(--theme-border-high-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.refresh-row__input {
  width: 3rem;
  min-height: 2.5rem;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--theme-foreground-high-contrast);
  text-align: right;
}

/* The number and its "minutes" suffix are drawn as one control, so the ring goes round the box
   rather than round the bare input inside it - which is why the input may drop its own and this
   one has to take it. */
.refresh-row__input:focus-visible {
  outline: none;
}

.refresh-row__every:has(.refresh-row__input:focus-visible) {
  outline: 2px solid var(--theme-accent);
  outline-offset: 0;
  border-color: var(--theme-accent);
}

.refresh-row__suffix {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

/*
 * A real checkbox drawn as a switch. It stays a checkbox rather than becoming a styled div with
 * a click handler, because the form submits it and because Rails renders a hidden companion
 * beside it that setSectionShown has to be able to disable when the section is hidden.
 */
.switch-row {
  display: flex;
  align-items: flex-start;
  gap: var(--size-inset);
  padding: var(--size-inset) var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
  cursor: pointer;
}

/* The row lifts to the neutral alt fill when the switch is on. It used to take the accent tint,
   which now argues with the green track: two colours saying "on" is one too many. */
.switch-row:has(.switch-row__input:checked) {
  border-color: var(--theme-border-high-contrast);
  background: var(--theme-background-container-alt);
}

/*
 * The track is 40 by 22 with a 16px knob inset 3px, which leaves the knob exactly 18px of travel:
 * 40 - 16 - 3 - 3. Every one of those is written off the grid tokens rather than as a number, so
 * the three that are not multiples of 8 - the 22, the 3 and the 18 - stay derived from the half
 * step and the inset instead of being four loose pixel values nobody can check.
 */
.switch-row__input {
  position: relative;
  width: var(--size-5x);
  height: calc(var(--size-half) * 5.5);
  flex: 0 0 auto;
  margin: var(--size-half) 0 0;
  appearance: none;
  -webkit-appearance: none;
  border-radius: 999px;
  background: var(--theme-border-low-contrast);
  cursor: pointer;
  transition: background var(--duration-colour) var(--bezier);
}

.switch-row__input::after {
  content: "";
  position: absolute;
  top: calc(var(--size-inset) / 4);
  left: calc(var(--size-inset) / 4);
  width: var(--size-2x);
  height: var(--size-2x);
  border-radius: 999px;
  background: var(--theme-background-container);
  transition: transform var(--duration-transform) var(--bezier);
}

/* On is the semantic success colour, not the accent: the accent already means "this is the thing
   to press" everywhere else on the page. */
.switch-row__input:checked {
  background: var(--theme-accent);
}

.switch-row__input:checked::after {
  transform: translateX(calc(var(--size-half) * 4.5));
}

.switch-row__input:disabled {
  cursor: not-allowed;
  background: var(--theme-background-container-alt);
}

.switch-row__input:focus-visible {
  outline: 2px solid var(--theme-accent);
  outline-offset: 2px;
}

.switch-row__copy {
  display: grid;
  gap: var(--size-half);
  min-width: 0;
}

.switch-row__title {
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.switch-row__text {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  line-height: 1.5;
}

.rail-card {
  padding: var(--size-2x) var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.rail-card > * + * {
  margin-top: var(--size-2x);
}

.rail-card__head {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  color: var(--theme-accent);
}

.rail-card__title {
  margin: 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
}

.rail-card__title--plain {
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  text-transform: none;
}

.summary-list {
  margin: 0;
}

.summary-row {
  display: flex;
  align-items: flex-start;
  gap: var(--size-1x);
  padding: var(--size-1x) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.summary-row__label {
  flex: 0 0 4.6rem;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.summary-row__value {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.summary-row__value--muted {
  color: var(--theme-foreground-low-contrast);
  font-weight: var(--font-weight-medium);
}

.plan-state {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  margin: 0;
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
}

.plan-state__dot {
  width: var(--size-half);
  height: var(--size-half);
  flex: 0 0 auto;
  border-radius: 999px;
  background: currentColor;
}

.plan-state--published .plan-state__dot {
  background: var(--theme-foreground-success);
}

.plan-state--missing .plan-state__dot {
  background: var(--theme-foreground-caution);
}

.plan-state--invalid .plan-state__dot {
  background: var(--theme-foreground-error);
}

.btn-soft {
  border-color: var(--theme-border-low-contrast);
  background: transparent;
  color: var(--theme-accent);
}

.btn-soft:hover,
.btn-soft:focus-visible {
  border-color: var(--theme-accent);
  background: var(--theme-background-accent-muted);
  color: var(--theme-accent);
}

.btn-block {
  width: 100%;
}

.form-note {
  margin: 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  line-height: 1.5;
}

.checklist {
  display: grid;
  gap: var(--size-1x);
  margin: 0;
  padding: 0;
  list-style: none;
}

.checklist__item {
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-20);
}

.checklist__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--size-half) * 4.5);
  height: calc(var(--size-half) * 4.5);
  flex: 0 0 auto;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: 999px;
  background: var(--theme-background-container);
  color: transparent;
}

.checklist__mark .icon {
  width: 0.7rem;
  height: 0.7rem;
  stroke-width: 3.2;
}

.checklist__item.is-done {
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

.checklist__item.is-done .checklist__mark {
  border-color: var(--theme-foreground-success);
  background: var(--theme-foreground-success);
  color: var(--theme-foreground-inverse);
}

@media (min-width: 600px) {

  .mode-guide {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .pair-row {
    grid-template-columns: minmax(0, 1fr) 1.75rem minmax(0, 1fr);
  }

  .pair-row__arrow {
    display: flex;
  }
}

@media (min-width: 900px) {
  .mode-guide {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

}

@media (min-width: 1200px) {
  .feed-form__layout {
    grid-template-columns: minmax(0, var(--form-max-width)) minmax(0, 21rem);
    justify-content: start;
    gap: var(--size-6x);
  }

  .feed-form__rail {
    position: sticky;
    top: calc(var(--topbar-height) + var(--size-6x));
  }
}

.filter-form {
  display: grid;
  gap: var(--size-inset);
  align-items: start;
}

.filter-form__field {
  gap: var(--size-half);
  padding: var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.filter-form__field--search {
  background: var(--theme-background-container);
  grid-column: 1 / -1;
}

.filter-form__field .form-control {
  min-height: var(--size-5x);
}

.filter-form__actions {
  align-items: center;
  justify-content: flex-start;
  padding-top: var(--size-half);
}

.form-control-inline {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--size-1x);
}

.form-control-inline__suffix {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.filter-form__bar {
  display: grid;
  gap: var(--size-inset);
  padding: var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.filter-form__bar-label {
  margin-bottom: var(--size-half);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  text-transform: uppercase;
}

.filter-form__bar-input {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: var(--size-1x);
}

.filter-form__bar-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 999px;
  color: var(--theme-foreground-low-contrast);
  background: var(--theme-background-container-alt);
  flex: 0 0 auto;
}

.filter-form__bar-icon .icon {
  width: 0.95rem;
  height: 0.95rem;
}

/* The FIELD measure, not the form one, and for the same reason `.settings-form` takes it: nothing
   on these forms is wider than a field, so a 912px cap only ran each section's hairline 272px past
   the right edge of every box under it. Capped here, the rule, the labels and the inputs share one
   right edge. The 912px cap stays on `--form-max-width` for a form that really does lay fields out
   side by side; none of these does. */
.form-card {
  max-width: var(--field-width);
  margin: 0;
}

/*
 * One section per group of five to seven fields, separated by 40px of air and nothing else. The
 * frame this used to draw round each one said "these things are separate", which is the opposite
 * of what a form is; the rule that says "a new section starts here" now sits under the legend,
 * where it belongs, and drawing it twice would only be louder.
 */
.form-section {
  padding: 0;
  border-top: 0;
}

.form-section + .form-section {
  margin-top: var(--size-5x);
}

/*
 * Every section of every form is a real fieldset now, and a fieldset arrives with a frame, its
 * own padding and `min-inline-size: min-content` from the browser. The last of those is the one
 * that does damage: a fieldset refuses to be narrower than its widest unbreakable content, so an
 * unreset one blows `.feed-form__main` out of its grid column. The frame goes because a screen
 * reader gets the grouping from the element and its legend, and the eye gets it from the air
 * above; a card that draws its own border (`.form-step`) sets it back by class.
 */
fieldset {
  min-width: 0;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* Section step, not field step: a fieldset carrying a single field's options is a `.form-group`
   and is spaced as one field, which is why that case is excluded here. */
fieldset + fieldset:not(.form-group) {
  margin-top: var(--size-5x);
}

/*
 * A legend is the caption of its fieldset, and it is the only element in the language that a
 * browser is allowed to lay out its own way: several engines ignore `display` on it and keep it
 * a block. So this rule sets only what every engine honours - the width, and the inline padding
 * a browser adds by itself - and each legend's own class supplies the look. Where that look is a
 * flex row (`.form-step__head`), `.form-step__title` is set inline below so the row still reads
 * as a row in an engine that refused the flex, instead of the title dropping under the number.
 */
.form-step__legend {
  display: block;
  float: none;
  inline-size: 100%;
  padding-inline: 0;
  padding-block-end: var(--size-inset);
  margin-block-end: var(--size-3x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.form-step__legend .form-step__title {
  display: block;
  margin: 0;
}

.form-step__legend .form-step__lede {
  display: block;
  margin-top: var(--size-half);
}

.form-step__number {
  display: none;
}


/* 17px at weight 500, 12px of air under the words, then the hairline. Everything below the rule
   belongs to this heading. */
/* ONE shape for a section heading, on every form: the name at 17px and weight 500, in the ink
   everything else is written in, with a hairline under it and 24px of air below that. No icon and
   no accent colour - each legend carried a blue glyph, which made the decoration of a group louder
   than the words of the fields inside it, and gave the accent a second meaning beside "this is the
   thing to press". The block below is what `.settings-section__title` had already moved to; this
   brings the other five forms to it rather than leaving one screen right and five different. */
.form-section-title {
  display: block;
  width: 100%;
  margin: 0 0 var(--size-3x);
  padding: 0 0 var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.form-callout {
  display: grid;
  gap: var(--size-half);
  margin: var(--size-2x) 0;
  padding: var(--size-2x) var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-accent-subtle);
}

.form-callout__title {
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
  color: var(--theme-foreground-high-contrast);
}

.form-callout__text {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
  line-height: 1.5;
}

/*
 * The spacing ladder, which is the whole grammar of a form: 8px from a label to its control and
 * from that control to its helper text, 24px from one field to the next, 40px between sections,
 * 48px before the actions row. Each step is at least 1.5x the one under it, so the eye can tell
 * where one field ends. The label is pulled toward the thing it names - a big gap above it and a
 * small one below - because a label sitting the same distance from both belongs to neither.
 */
/* No margin reset here: a div has none to reset, and `margin: 0` sits later in the file than the
   ladder rules above, so it was quietly winning and collapsing the 24px between a field and
   whatever preceded it. */
.form-group {
  display: grid;
  gap: var(--size-1x);
  max-width: var(--field-width);
}

/* The width modifiers stay in the markup but resolve to the same measure: one width for every
   field was asked for explicitly, so a per-field cap would only bring the ragged edges back. */
.form-group--sm,
.form-group--md {
  max-width: var(--field-width);
}

/* A block a person acts on is capped with them, so the toggle row and the callout end where the
   inputs end instead of running past them. */
.form-step > .switch-row,
.form-step > .form-callout,
.form-step > .checkbox-group,
.form-step > .toggle-row,
.form-step > .request-block,
.form-step > .advanced-block,
.form-step > .refresh-row,
.form-step > .form-row {
  max-width: var(--field-width);
}

.field-grid > .form-group + .form-group {
  margin-top: 0;
}

.form-group + .form-group,
.form-group + .field-grid,
.field-grid + .form-group,
.field-grid + .field-grid,
.form-group + .form-row,
.form-row + .form-group,
.form-group + .checkbox-group,
.checkbox-group + .form-group,
.form-group + .toggle-row,
.toggle-row + .form-group,
.form-group + .switch-row,
.switch-row + .form-group {
  margin-top: var(--size-3x);
}

/* 12px, medium contrast, regular weight. It names the field; the value below it is what the eye
   should land on, so the label never outweighs it. */
.form-group label,
.form-label {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
}

/*
 * One resting border for every control, one radius, no shadow. The value a person typed is the
 * darkest thing in the field: the control is high contrast where its label is medium and its
 * helper is low.
 */
.form-control {
  width: 100%;
  min-height: var(--size-5x);
  padding: 0 var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  box-shadow: none;
  transition: border-color var(--duration-colour) var(--bezier), background var(--duration-colour) var(--bezier);
}

.form-control:hover {
  border-color: var(--theme-foreground-low-contrast);
}

textarea.form-control {
  padding: var(--size-inset);
  min-height: calc(var(--size-6x) * 2);
  line-height: 1.4;
  resize: vertical;
}

/* A 2px outline rather than a recoloured border: a border change alone is invisible to somebody
   navigating by keyboard, which is the only way they can see where they are. */
.form-control:focus-visible {
  outline: 2px solid var(--theme-accent);
  outline-offset: 0;
  border-color: var(--theme-accent);
}

/* Disabled says "not now" quietly; read-only is DATA and stops looking like a control at all. */
.form-control:disabled {
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
  cursor: not-allowed;
}

.form-control[readonly] {
  background: var(--theme-background-container-alt);
  border-color: transparent;
}

/* `:user-invalid` and not `:invalid`: the second fires on an empty required field the moment the
   page loads, which paints a form red before anybody has typed a character. */
.form-control:user-invalid,
.form-control[aria-invalid="true"] {
  border-color: var(--theme-foreground-error);
}

.form-control:user-invalid:focus-visible,
.form-control[aria-invalid="true"]:focus-visible {
  outline-color: var(--theme-foreground-error);
}

.form-control::placeholder {
  color: var(--theme-foreground-low-contrast);
}

/* Helper text is the quietest thing in the field: 12px at low contrast, one step under the label,
   which is itself one step under the value. */
.form-text,
.form-help {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  line-height: 1.45;
}

.form-row {
  display: grid;
  gap: var(--size-2x);
}

.form-col,
.form-row .half,
.form-row .third {
  min-width: 0;
}

.form-col.flex-2 {
  min-width: 0;
}

.checkbox-group,
.toggle-row {
  display: flex;
  gap: var(--size-inset);
  align-items: flex-start;
  min-height: var(--size-5x);
  padding: var(--size-inset);
  border-radius: var(--radius);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
}

/*
 * The box is drawn rather than left to the operating system, so it carries the theme's own ink
 * instead of whatever blue the platform picked. 18px is the reference system's box size and is
 * the one measurement here that is not on the 8px grid, so it is written off the half step
 * rather than as a number: 4 x 4.5. The gap to the text is 12px and never less, or the box and
 * its words read as one blob.
 */
.checkbox-group input[type="checkbox"],
.checkbox-group input[type="radio"],
.toggle-row input[type="checkbox"]:not(.switch-row__input),
.toggle-row input[type="radio"],
.form-group input[type="checkbox"]:not(.switch-row__input),
.form-group input[type="radio"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--size-half) * 4.5);
  height: calc(var(--size-half) * 4.5);
  flex: 0 0 auto;
  /* Half the difference between the box and the 14px line beside it, so the two sit on one
     optical baseline rather than the box riding high in a top-aligned row. */
  margin: calc(var(--size-half) / 2) 0 0;
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: calc(var(--size-inset) / 4);
  background: var(--theme-background-container);
  color: var(--theme-foreground-inverse);
  font-size: var(--font-size-10);
  line-height: 1;
  cursor: pointer;
  transition: background var(--duration-colour) var(--bezier), border-color var(--duration-colour) var(--bezier);
}

.checkbox-group input[type="radio"],
.toggle-row input[type="radio"],
.form-group input[type="radio"] {
  border-radius: 50%;
}

.checkbox-group input[type="checkbox"]:hover,
.checkbox-group input[type="radio"]:hover,
.toggle-row input[type="checkbox"]:not(.switch-row__input):hover,
.toggle-row input[type="radio"]:hover,
.form-group input[type="checkbox"]:not(.switch-row__input):hover,
.form-group input[type="radio"]:hover {
  border-color: var(--theme-foreground-low-contrast);
}

/* Checked is a filled box in the darkest ink the theme has, with the mark drawn in the inverse
   colour on top of it - the same pair the reference system uses, and not the accent, so a page
   of options does not read as a page of links. */
.checkbox-group input[type="checkbox"]:checked,
.checkbox-group input[type="radio"]:checked,
.toggle-row input[type="checkbox"]:not(.switch-row__input):checked,
.toggle-row input[type="radio"]:checked,
.form-group input[type="checkbox"]:not(.switch-row__input):checked,
.form-group input[type="radio"]:checked {
  border-color: var(--theme-foreground-high-contrast);
  background: var(--theme-foreground-high-contrast);
}

/* The tick is an `::after` on the input, which is what the reference system draws it with. A
   browser that declines to render a pseudo-element on an input shows the filled box and no tick,
   which still reads as on, because the fill above is what carries the state. */
.checkbox-group input[type="checkbox"]:checked::after,
.toggle-row input[type="checkbox"]:not(.switch-row__input):checked::after,
.form-group input[type="checkbox"]:not(.switch-row__input):checked::after {
  content: "\2713";
  color: var(--theme-foreground-inverse);
}

/* The radio's dot is painted into the box's own background instead, so it does not depend on that
   same pseudo-element: a filled circle with no dot in it would read as a checkbox. */
.checkbox-group input[type="radio"]:checked,
.toggle-row input[type="radio"]:checked,
.form-group input[type="radio"]:checked {
  background-image: radial-gradient(circle at center,
    var(--theme-foreground-inverse) 0 var(--size-half),
    transparent var(--size-half) 100%);
}

.checkbox-group input[type="checkbox"]:disabled,
.checkbox-group input[type="radio"]:disabled,
.toggle-row input[type="checkbox"]:not(.switch-row__input):disabled,
.toggle-row input[type="radio"]:disabled,
.form-group input[type="checkbox"]:not(.switch-row__input):disabled,
.form-group input[type="radio"]:disabled {
  background: var(--theme-background-container-alt);
  border-color: var(--theme-border-low-contrast);
  cursor: not-allowed;
}

/* The words are the hit area, not the 18px box beside them, so the label carries the pointer and
   reads at the body size rather than as a caption. */
.checkbox-group label,
.toggle-row label {
  margin: 0;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-regular);
  cursor: pointer;
}

/* A stack of options is tighter than a stack of fields: 8px, where field to field is 24px, so the
   options still read as one group. A ROW of them is looser than the stack at 24px, because
   horizontal proximity reads as closer than vertical at the same distance. */
.checkbox-group + .checkbox-group,
.toggle-row + .toggle-row {
  margin-top: var(--size-1x);
}

.checkbox-group--inline,
.toggle-row--inline {
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-3x);
}

/*
 * The largest step in the ladder sits here on purpose: a rule and 48px of air, so nobody submits
 * by reflex while still reading the last field.
 *
 * The row is RIGHT aligned, with the primary action last in reading order. That is a deliberate
 * divergence from form-design-brief section 10, which puts the primary action first and on the
 * left; it is Kamil's call, and it matches where this app already puts the save button in the
 * feed form's sticky header (`.feed-form__actions`), which is unchanged.
 */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--size-inset);
  margin-top: var(--size-6x);
  padding-top: var(--size-3x);
  border-top: 1px solid var(--theme-border-low-contrast);
}

/* A destructive action is not part of the pair, so it is pushed to the far end of the row - the
   LEFT end here, because the pair it has to stay away from sits on the right. */
.form-actions .btn-danger {
  margin-right: auto;
}

/*
 * A field's own error, sitting in the line the helper text occupies so the field does not jump
 * when one appears. Colour is never the signal on its own: the flex row is there to hold a mark
 * or a word in front of the message, which is what somebody who cannot see the red is reading.
 * The message says how to fix it - "Enter the date as DD.MM.YYYY", never "Invalid input".
 */
.form-error-text {
  display: flex;
  align-items: flex-start;
  gap: var(--size-1x);
  color: var(--theme-foreground-error);
  font-size: var(--font-size-10);
  line-height: 1.45;
}

/* The prefix carries the meaning the colour is only repeating, so it is drawn even where the red
   is not seen at all. */
.form-error-text::before {
  content: "\26A0";
  flex: 0 0 auto;
  font-size: var(--font-size-10);
  line-height: 1.45;
}

.form-error-text .icon {
  flex: 0 0 auto;
  width: var(--size-2x);
  height: var(--size-2x);
}

/* An icon written into the markup replaces the generated prefix rather than doubling it. */
.form-error-text:has(.icon)::before {
  content: none;
}

/* The summary at the top of a form that failed to save: the same red and the same 12px as the
   per-field message, so the two read as one voice rather than two. */
.form-error {
  display: grid;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-2x);
  margin-bottom: var(--size-3x);
  border-radius: var(--radius-sm);
  background: var(--theme-background-error-subtle);
  border: 1px solid var(--theme-foreground-error);
  color: var(--theme-foreground-error);
}

.form-error__title {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.form-error ul {
  margin: 0;
  padding-left: var(--size-3x);
  font-size: var(--font-size-10);
  line-height: 1.45;
}

.settings-stack {
  gap: var(--size-inset);
}

/* The FIELD measure, not the form one. Nothing on this screen is wider than a field, so a 912px
   cap only made the section rules and the actions rule run 272px past the right edge of every box
   under them - three lines belonging to nothing. Capped here, the rule, the labels and the inputs
   share one right edge. */
.settings-form {
  max-width: var(--field-width);
}

/* No gap of its own. A grid's gap ADDS to the adjacency margins that space fields in every other
   form here rather than replacing them, so the two token fields sat 48px apart while the password
   pair beside them sat at 24. One rule spaces fields on this screen, and it is the same one that
   spaces them on the others. */
.settings-section {
  display: grid;
  padding-block: 0;
}

.settings-section + .settings-section {
  margin-top: var(--size-5x);
}

/* The same hairline every other section heading carries. It is a <legend> now, so it takes the
   grid it always had - a grid, unlike a flex row, degrades to the same single column in an engine
   that refuses `display` on a legend.

   It keeps `.form-step__legend`'s 24px of air UNDER the rule, where it used to zero it. Zeroed, the
   hairline sat 14px above the first field's LABEL and 26px above its input, so the label was nearer
   the line that opens the section than the box it names and read as belonging to the line. A
   section rule has to stand further from what follows it than a label stands from its own control,
   or the ladder inverts and the group stops reading as a group. */
.settings-section__header {
  display: grid;
  gap: var(--size-half);
  padding-bottom: var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

/* The sentence under the section's name, at the same 12px and the same quiet ink as a field's
   helper text, because that is what it is: help for the group rather than a paragraph of prose. */
.settings-section__text {
  max-width: 60ch;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* The section size the reference gives a legend: 17px at weight 500, no icon and no accent. */
.settings-section__title {
  margin: 0;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.settings-token-input {
  min-height: var(--size-5x);
  font-family: var(--font-mono);
  font-size: var(--font-size-30);
}

.settings-form__actions {
  justify-content: flex-end;
}

/* Centred in the space it is given, with a measure it cannot outgrow. It used to sit hard against
   the top-left corner of a full-width panel with its action thrown to the opposite corner, which
   read as a layout that had lost its content rather than as a list with nothing in it. */
.empty-state {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--size-inset);
  text-align: center;
  padding: var(--size-6x) var(--size-3x);
}

.empty-state__text {
  max-width: 44ch;
}

.empty-state__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
}

.empty-state__title {
  margin: 0;
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.empty-state__text {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
}

.stats-grid {
  display: grid;
  /* Two from the narrowest screen up: a counter card is a number and two short lines, so
     one per row spent a phone's whole first screen on four of them and pushed the list
     that the page is for below the fold. `min-width: 900px` widens this to four. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--size-inset);
  margin-bottom: var(--size-2x);
}

.stat-card,
.metric-card {
  padding: var(--size-2x) var(--size-2x);
}

.stat-card {
  display: grid;
  gap: var(--size-half);
  min-height: 6.5rem;
  align-content: start;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
}

.stat-label {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

.stat-value,
.metric-value {
  display: block;
  margin-top: var(--size-1x);
  font-size: var(--font-size-70);
  line-height: 1;
  font-weight: var(--font-weight-medium);
}

.stat-note,
.metric-note {
  display: block;
  margin-top: var(--size-1x);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.stats-grid--offers .stat-card {
  gap: var(--size-half);
  min-height: 7.1rem;
  padding: var(--size-inset) var(--size-2x);
  align-content: start;
}

.stats-grid--offers .stat-value {
  margin-top: var(--size-half);
  font-size: var(--font-size-60);
}

.stats-grid--offers .stat-note {
  margin-top: var(--size-half);
  font-size: var(--font-size-10);
  line-height: 1.35;
}

/* The section step above and below it: the frame says where the widget is, the air says it is a
   thing of its own between the counters over it and the table under it. */
.chart-card {
  display: grid;
  gap: var(--size-2x);
  margin: var(--size-3x) 0 var(--size-5x);
}

.chart-card--offers {
  max-width: none;
}

.chart-card--empty {
  min-height: 18rem;
}

.chart-header {
  display: grid;
  gap: var(--size-half);
}

.chart-header--split {
  align-items: start;
}

.chart-title {
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.chart-subtitle {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
}

.chart-mobile-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--size-inset);
}

.chart-mini-stat {
  display: grid;
  gap: var(--size-half);
  padding: var(--size-inset) var(--size-inset);
  border-radius: var(--radius);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
}

.chart-mini-stat__label {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

.chart-mini-stat__value {
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-50);
  line-height: 1;
  font-weight: var(--font-weight-medium);
}

.chart-mini-stat__note {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

.chart-plot {
  display: grid;
  gap: var(--size-half);
  /* The gutter holds the y tick labels, so it has to fit the widest number this feed reaches.
     It is a CSS length rather than a computed one because the labels are HTML now: they are
     laid out by the browser instead of being placed at a coordinate the server guessed. */
  --chart-gutter: 3.1rem;
  --chart-height: clamp(190px, 20vw, 280px);
}

.chart-plot__body {
  position: relative;
  height: var(--chart-height);
  padding-left: var(--chart-gutter);
}

.chart-plot__gridline {
  position: absolute;
  left: var(--chart-gutter);
  right: 0;
  height: 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.chart-plot__ytick {
  position: absolute;
  right: calc(100% + 0.55rem);
  transform: translateY(-50%);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.chart-plot__canvas {
  position: absolute;
  inset: 0 0 0 var(--chart-gutter);
}

/* The one element that is allowed to stretch. `preserveAspectRatio="none"` is what makes the
   percentage geometry work at any width, and it is also why nothing but the curve may live in
   here: a circle would come out an ellipse and a letter would come out stretched. */
.chart-plot__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.chart-plot__area {
  fill: url(#chart-plot-area);
}

.chart-plot__line {
  fill: none;
  stroke: var(--theme-accent);
  stroke-width: 1.75;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.chart-plot__point {
  position: absolute;
  width: 9px;
  height: 9px;
  transform: translate(-50%, 50%);
  border-radius: 999px;
  border: 1.5px solid var(--theme-accent);
  background: var(--theme-background-container);
  cursor: pointer;
  transition: background var(--duration-colour) var(--bezier), transform var(--duration-transform) var(--bezier);
}

.chart-plot__point:hover,
.chart-plot__point:focus-visible {
  background: var(--theme-accent);
  transform: translate(-50%, 50%) scale(1.25);
}

.chart-plot__tip {
  position: absolute;
  bottom: calc(100% + 0.5rem);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--size-half) var(--size-1x);
  border-radius: var(--radius);
  background: var(--theme-background-inverse);
  color: var(--theme-foreground-inverse);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-opacity) var(--bezier);
}

/* A tooltip on the first or last point would hang off the card, so those two are anchored to
   their own edge instead of being centred on the point. */
.chart-plot__tip--start {
  left: 0;
  transform: none;
}

.chart-plot__tip--end {
  left: auto;
  right: 0;
  transform: none;
}

.chart-plot__point:hover .chart-plot__tip,
.chart-plot__point:focus-visible .chart-plot__tip {
  opacity: 1;
}

.chart-plot__axis {
  position: relative;
  height: 1.05rem;
  margin-left: var(--chart-gutter);
}

.chart-plot__xtick {
  position: absolute;
  transform: translateX(-50%);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (max-width: 599px) {
  .chart-plot {
    --chart-gutter: 2.4rem;
  }

  /* Fifty-two dots across a phone screen are closer together than they are wide, so they read
     as a thick band rather than as points, and the tooltip they carry cannot be aimed at with a
     finger anyway. The line and the axis still answer what the card is for. */
  .chart-plot__point {
    display: none;
  }

  .chart-plot__xtick--dense {
    display: none;
  }
}

.chart-mobile-list {
  display: grid;
  gap: var(--size-inset);
}

.chart-mobile-row {
  display: grid;
  gap: var(--size-1x);
}

.chart-mobile-row__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--size-inset);
}

.chart-mobile-row__label {
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.chart-mobile-row__value {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  white-space: nowrap;
}

.chart-mobile-row__track {
  position: relative;
  height: 0.6rem;
  border-radius: 999px;
  background: var(--theme-background-container-alt);
  overflow: hidden;
}

.chart-mobile-row__fill {
  display: block;
  height: 100%;
  min-width: 0.8rem;
  border-radius: inherit;
  background: var(--theme-accent);
}

.chart-desktop-timeline {
  display: none;
}

.chart-scroll-container {
  overflow-x: auto;
  padding: 0 0 var(--size-1x);
  scrollbar-gutter: stable both-edges;
}

.chart-container {
  display: flex;
  align-items: end;
  gap: var(--size-inset);
  min-width: 34rem;
  height: 14rem;
}

.chart-dense {
  min-width: 42rem;
}

.chart-bar-group {
  flex: 1;
  display: grid;
  gap: var(--size-1x);
  min-width: 2.1rem;
}

.chart-bar-wrapper {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: var(--size-1x);
  height: 11rem;
}

.chart-bar-value {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

/* A thin rounded line rather than a filled column: the value is read off the height, and a
   full-width block at this density is mostly ink. */
.chart-bar {
  width: 0.55rem;
  margin-inline: auto;
  border-radius: 999px;
  min-height: 0.4rem;
  background: var(--theme-accent);
  transition: filter var(--duration-colour) var(--bezier);
}

.chart-bar:hover {
  transform: none;
  filter: brightness(1.08);
}

.chart-bar-label {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  text-align: center;
}

.chart-note {
  margin: 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* The same shape as `.empty-state`, for the same reason: this one sat in the top-left of the
   whole chart area. */
.chart-empty {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: var(--size-2x);
  min-height: 13rem;
  padding: var(--size-4x) var(--size-3x);
  text-align: center;
}

.chart-empty__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
}

.chart-empty__title {
  margin: 0;
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.chart-empty__text {
  margin: 0;
  max-width: 44ch;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
  line-height: 1.55;
}

.chart-chip {
  display: none !important;
}

@media (max-width: 899px) {
  .chart-header--split {
    gap: var(--size-inset);
  }

}

.table-card {
  overflow-x: auto;
  overflow-y: visible;
  -webkit-overflow-scrolling: touch;
}

.data-table {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
}

.sortable {
  display: inline-flex;
  align-items: center;
  gap: var(--size-half);
  color: inherit;
}

.sortable::after {
  content: "↕";
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.sortable.asc::after {
  content: "↑";
  color: var(--theme-accent);
}

.sortable.desc::after {
  content: "↓";
  color: var(--theme-accent);
}

/* Below 600px a table is a LIST: one block per row, separated by the same 1px rule the rows carry
   on a wide screen, and no box round any of them. The first cell is the row's heading at 14px,
   every other cell is one "label: value" line at 12px with the label taken from the cell's own
   data-label, and the Actions cell is a row of compact buttons at the end. Nothing scrolls
   sideways: a phone reading eleven columns through a 390px slot saw three of them. */
.data-table thead {
  display: none;
}

.data-table tbody {
  display: grid;
  gap: 0;
  padding: 0;
}

.data-table tbody tr {
  display: grid;
  gap: var(--size-half);
  padding: var(--size-inset) 0;
  border: 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
  border-radius: 0;
  background: var(--theme-background-container);
  box-shadow: none;
}

.data-table tbody tr:hover {
  background: var(--theme-background-container);
}

.data-table tbody tr.feed-disabled {
  opacity: 0.46;
}

/* A flex row rather than inline text, so a cell whose value is a stack or a badge list still sits
   on the label's line and a long value wraps under it. */
.data-table td {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 var(--size-half);
  min-width: 0;
  padding: 0;
  border: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  line-height: 1.4;
}

.data-table td::before {
  content: attr(data-label) ":";
  flex: 0 0 auto;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
}

.data-table td:first-child {
  display: block;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
}

.data-table td:first-child::before,
.data-table td[data-label="Actions"]::before {
  display: none;
}

.data-table td[data-label="Actions"] {
  margin-top: var(--size-half);
}

.data-table td .form-control {
  flex: 1 1 100%;
  min-width: 0;
}

/* The 12px line stays 12px whatever the cell holds: a row's value class, a code slug, a badge. */
.data-table td .cell-secondary {
  margin-top: 0;
}

.data-table td:first-child .cell-secondary {
  margin-top: var(--size-half);
}

.data-table td:first-child .cell-primary {
  font-size: var(--font-size-30);
}

.data-table td .cell-primary {
  font-size: inherit;
  color: var(--theme-foreground-high-contrast);
}

/* A classic table shows its head and its columns at every width from 600px up; below that it
   stacks like every other table, so the whole of this is gated on the tablet tier. */
@media (min-width: 600px) {
.data-table--classic {
  table-layout: auto;
  min-width: 66rem;
  width: max(100%, 66rem);
}

/* The identifying column carries a title and a slug, so it is given room to be read on one or
   two lines instead of wrapping into five while ten narrow columns keep their whitespace. */
.data-table--classic th:first-child,
.data-table--classic td:first-child {
  min-width: 12.5rem;
}

/* The outer columns KEEP their inset, the same 12px every other cell has. Zeroing it put the
   first column flush on the page's left edge, which aligned the header with the page title and
   then ran the hovered row's fill hard into its own text - a band with no air before the words it
   highlights. The reference insets every cell for the same reason. */

.data-table--classic thead {
  display: table-header-group;
}

.data-table--classic tbody {
  display: table-row-group;
  padding: 0;
}

.data-table--classic tbody tr {
  display: table-row;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.data-table--classic tbody tr:hover {
  background: transparent;
}

/* A column header is a caption, not a heading: 12px at regular weight and low contrast, so the
   values under it are what the eye reads. It is declared here as well as in the desktop tier
   because a classic table shows its head at every width. */
.data-table--classic th {
  padding: var(--size-1x) var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-align: left;
  text-transform: uppercase;
  background: var(--theme-background-container);
}

.data-table--classic td,
.data-table--classic td:first-child {
  display: table-cell;
  grid-template-columns: none;
  gap: 0;
  padding: var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
  vertical-align: middle;
  color: inherit;
  font-size: var(--font-size-30);
  line-height: 1.45;
}

.data-table--classic td::before {
  display: none;
}

.data-table--classic td .cell-secondary {
  margin-top: var(--size-half);
}
}

/* The last row keeps its rule. It used to drop it because the wrapper card's own bottom edge
   closed the table off; with no wrapper, dropping it leaves the table trailing away into the
   page. */

.data-table--classic tbody tr:hover td {
  background: var(--theme-background-container-alt);
}

.rss-feed-row {
  cursor: pointer;
}

.rss-feed-row:focus-visible td {
  background: var(--theme-background-accent-subtle);
}

.data-table--classic tbody tr.feed-disabled td {
  background: var(--theme-background-base);
  color: var(--theme-foreground-medium-contrast);
}

.cell-primary {
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

/* The primary cell of a row is dark type. It was the accent colour purely because it is a link,
   which put a column of blue beside every colour the state badges were already spending. */
.cell-primary a {
  color: inherit;
}

.cell-primary a:hover {
  color: inherit;
  text-decoration: underline;
}

/* Row actions read as text and take a border only under the pointer, so a table of ten rows is
   not a wall of chips. */
.action-cell--table .btn {
  border-color: transparent;
  background: transparent;
  color: var(--theme-foreground-medium-contrast);
}

.action-cell--table .btn:hover,
.action-cell--table .btn:focus-visible {
  border-color: var(--theme-border-low-contrast);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
}

.action-cell--table .btn-danger {
  background: transparent;
  color: var(--theme-foreground-medium-contrast);
}

.action-cell--table .btn-danger:hover,
.action-cell--table .btn-danger:focus-visible {
  border-color: var(--theme-border-low-contrast);
  background: var(--theme-background-error-subtle);
  color: var(--theme-foreground-error);
}

.cell-secondary {
  margin-top: var(--size-half);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.cell-code {
  display: inline-flex;
  align-items: center;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
}

/*
 * State is a coloured DOT beside ordinary text, not a tinted chip. A row carries several of
 * these at once (health, storage, deep scraping, item count), and as filled chips they turned
 * every row into a colour chart in which nothing stood out. The dot keeps the state readable
 * and hands the emphasis back to the type: weight and darkness say what matters, colour only
 * says which state it is.
 */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
}

.status-badge::before {
  content: "";
  width: 0.4rem;
  height: 0.4rem;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--theme-border-high-contrast);
}

.status-badge.success::before {
  background: var(--theme-foreground-success);
}

/* The two states somebody has to act on are darker and heavier than the rest of the row rather
   than a different colour block: the dot says which one it is. */
.status-badge.warning,
.status-badge.error {
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

.status-badge.warning::before {
  background: var(--theme-foreground-caution);
}

.status-badge.error::before {
  background: var(--theme-foreground-error);
}

.status-badge.muted::before {
  background: var(--theme-border-high-contrast);
}

.status-badge.neutral::before {
  background: var(--theme-accent);
}

/* A type is not a state, so it carries no dot: it is a micro label in the interface font. */
/* A VALUE of the row, so it is read in the row's own ink. It was the caption grey at 12px, which
   put "HTTP JSON" - the answer to what kind of source this is - fainter than the column header
   naming the question. Upper case stays, because that is what tells the two settings apart from
   free text; the contrast is what was wrong. */
.type-badge {
  display: inline-flex;
  align-items: center;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
  white-space: nowrap;
}

.badge-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-half);
}

.meta-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.meta-dot {
  color: var(--theme-foreground-low-contrast);
}

.error-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--size-half);
  color: var(--theme-foreground-error);
  font-size: var(--font-size-10);
}

.error-trigger-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.error-details {
  position: relative;
}

.health-trigger {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  list-style: none;
}

.health-trigger::-webkit-details-marker {
  display: none;
}

.health-trigger .status-badge {
  transition: transform var(--duration-transform) var(--bezier), box-shadow var(--duration-colour) var(--bezier);
}

.health-trigger:hover .status-badge,
.health-trigger:focus-visible .status-badge,
.error-details[open] .health-trigger .status-badge {
  transform: translateY(-1px);
  box-shadow: 0 0 0 3px var(--theme-background-error-subtle);
}

.error-panel {
  display: grid;
  gap: var(--size-inset);
  margin-top: var(--size-inset);
  padding: var(--size-2x);
  border-radius: var(--radius);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
  box-shadow: var(--shadow-md);
  max-height: min(70vh, 32rem);
  overflow: auto;
  overscroll-behavior: contain;
  overflow-wrap: anywhere;
  word-break: break-word;
}

.error-list {
  display: grid;
  gap: var(--size-inset);
}

.error-item {
  display: grid;
  gap: var(--size-half);
}

.error-item strong {
  font-size: var(--font-size-10);
  text-transform: uppercase;
  color: var(--theme-foreground-low-contrast);
}

.error-item span,
.error-item code {
  font-size: var(--font-size-30);
  line-height: 1.45;
  color: var(--theme-foreground-high-contrast);
}

.error-item code {
  padding: var(--size-1x) var(--size-1x);
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
}

.error-panel .error-trigger-text {
  display: block;
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.5;
}

.error-panel textarea {
  width: 100%;
  min-height: 10rem;
  padding: var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-10);
  font-family: var(--font-mono);
}

.pagination-wrapper {
  display: grid;
  gap: var(--size-inset);
  justify-items: center;
  margin-top: var(--size-2x);
}

.pagination-info {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-20);
}

.pagy {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--size-1x);
}

.pagy a,
.pagy span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.1rem;
  height: 2.1rem;
  padding: 0 var(--size-inset);
  border-radius: var(--radius-sm);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
  color: var(--theme-foreground-medium-contrast);
}

.pagy a:hover {
  background: var(--theme-background-accent-subtle);
  color: var(--theme-accent);
}

.pagy span.current,
.pagy a.current {
  background: var(--theme-accent);
  color: var(--theme-foreground-inverse);
  border-color: transparent;
}

.details-grid {
  display: grid;
  gap: var(--size-2x);
}

.detail-card {
  overflow: hidden;
}

/* No inset now that there is no frame to be inset from: the heading of a section starts on the
   page's left edge, the same one the page title and every field start on. */
.detail-card__header {
  padding: 0 0 var(--size-half);
}

.detail-card__body {
  padding: 0;
}

/* A ROW of small facts, not a table of them. Six one-line values are a glance: a table gives each
   its own row and a label column a third of the width wide, which is right when the values are
   long and wrong when they are a word each. They wrap onto as many rows as the width allows. */
.fact-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--size-3x);
  margin: 0;
}

.fact {
  display: grid;
  gap: var(--size-half);
  min-width: 0;
}

.fact__label {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

.fact__value {
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
}

/* A long piece of prose somebody typed, at a measure it can be read at, clamped until it is asked
   for. The checkbox is the mechanism and is never seen: it is `sr-only` rather than `display:none`
   so it keeps its place in the tab order and announces its own state. */
.prose-block {
  display: grid;
  gap: var(--size-1x);
  justify-items: start;
}

.prose-block__text {
  margin: 0;
  max-width: 70ch;
  max-height: 18em;
  overflow: hidden;
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  /* The last visible line fades, which is what says there is more without a rule drawn under it. */
  mask-image: linear-gradient(to bottom, #000 calc(100% - 3em), transparent);
}

.prose-block__input:checked ~ .prose-block__text {
  max-height: none;
  mask-image: none;
}

/* The words are on the label rather than in the markup, because they are two states of one
   control and the markup would otherwise carry the one that happens to be false. */
.prose-block__toggle {
  color: var(--theme-accent);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
}

.prose-block__toggle::after {
  content: "Show the full criteria";
}

.prose-block__input:checked ~ .prose-block__toggle::after {
  content: "Show less";
}

.prose-block__input:focus-visible ~ .prose-block__toggle {
  outline: 2px solid var(--theme-accent);
  outline-offset: var(--size-half);
}

/* A button that opens onto the variants of its own action. `<details>` is the whole mechanism, so
   it opens with no script and closes on Escape by itself. */
.menu-button {
  position: relative;
  flex: 0 0 auto;
}

.menu-button__summary {
  list-style: none;
  cursor: pointer;
}

.menu-button__summary::-webkit-details-marker {
  display: none;
}

.menu-button__caret {
  transform: rotate(90deg);
  transition: transform var(--duration-colour) var(--bezier);
}

.menu-button[open] .menu-button__caret {
  transform: rotate(-90deg);
}

.menu-button__list {
  position: absolute;
  z-index: 30;
  top: calc(100% + var(--size-half));
  right: 0;
  display: grid;
  min-width: 12rem;
  padding: var(--size-half);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  background: var(--theme-background-container);
  box-shadow: var(--shadow-md);
}

.menu-button__item {
  padding: var(--size-1x) var(--size-inset);
  border-radius: var(--radius-sm);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  text-decoration: none;
  white-space: nowrap;
}

.menu-button__item:hover,
.menu-button__item:focus-visible {
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-high-contrast);
}

.detail-table {
  width: 100%;
  border-collapse: collapse;
}

.detail-table tr {
  border-top: 1px solid var(--theme-border-low-contrast);
}

.detail-table tr:first-child {
  border-top: none;
}

.detail-table th,
.detail-table td {
  display: block;
  width: 100%;
  padding: var(--size-inset) 0;
  text-align: left;
  vertical-align: top;
}

/* Stacked, a row is 12px, the label, 4px, the value, 12px and the rule. The value used to carry
   its own 12px on top as well, which put 16px between a label and the value it names and made
   every row as tall as two. */
.detail-table td {
  padding-top: 0;
}

/* The declared fields with nothing in them, as one caption line under the filled ones. */
.detail-unfilled {
  margin: 0;
  padding: var(--size-inset) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
  font-size: var(--font-size-10);
  line-height: 1.4;
}

.detail-table + .detail-unfilled {
  margin-top: 0;
}

.detail-unfilled:first-child {
  border-top: 0;
  padding-top: 0;
}

.detail-table th {
  padding-bottom: var(--size-half);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  text-transform: uppercase;
}

.code-frame {
  overflow: auto;
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
  border: 1px solid var(--theme-border-low-contrast);
}

.code-frame pre {
  margin: 0;
  padding: var(--size-2x);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-10);
  white-space: pre-wrap;
  word-break: break-word;
}

details summary {
  cursor: pointer;
  color: var(--theme-foreground-medium-contrast);
  font-weight: var(--font-weight-medium);
}

.summary-inline {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
}

.session-shell .main-content {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--size-2x);
  min-height: 100dvh;
  padding-block: var(--size-4x);
}

.session-shell .main-content .flash {
  position: fixed;
  top: 1.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 40;
  width: min(calc(100% - 2rem), 31rem);
  margin: 0;
}

.login-shell {
  width: min(100%, 31rem);
  padding: var(--size-3x);
}

.login-shell__hero,
.login-shell__brand {
  display: grid;
  gap: var(--size-inset);
  margin-bottom: var(--size-2x);
}

.login-shell__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--size-inset);
  margin-bottom: var(--size-2x);
}

.login-shell__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius);
  background: var(--theme-background-accent-subtle);
  color: var(--theme-accent);
  border: 1px solid var(--theme-border-low-contrast);
}

.login-shell__brand-name {
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
  color: var(--theme-foreground-high-contrast);
}

.login-shell__title {
  margin: 0;
  font-size: var(--font-size-70);
  line-height: 1;
  font-weight: var(--font-weight-medium);
}

.login-shell__text {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
}

.login-form {
  display: grid;
  gap: var(--size-2x);
}

/* The reset link is about the password field, so it sits in that field's helper line rather than
   under the whole card: last child of the `.form-group` grid, at the 12px `.form-text` size, and
   pushed to the field's right edge so it shares the field's own measure at every width. */
.login-form__aside {
  justify-self: end;
  font-size: var(--font-size-10);
}

.utility-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-inset);
  align-items: center;
}

/*
 * Text that is read aloud and never drawn: a table caption, the word "error" in front of a
 * message, the name of a control whose label is an icon. It is clipped rather than hidden,
 * because `display: none` and `visibility: hidden` take it out of the accessibility tree too and
 * then it says nothing to anybody. `.plan-sr-only` is the same utility under the extraction
 * editor's own prefix and is kept so that screen's markup does not have to change.
 */
.sr-only,
.plan-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.text-muted {
  color: var(--theme-foreground-low-contrast);
}

.text-secondary {
  color: var(--theme-foreground-medium-contrast);
}

/* Inside a table every cell holds a VALUE, and a value is the darkest thing in its row: the
   caption is the column header above it, at 12px in the quiet ink, and that contrast is the whole
   signal. Half the columns here reached for `.text-secondary`, which is a PAGE utility meaning
   "less important than the sentence beside you", so "about 2 months ago" and "Never" were printed
   fainter than the word CREATED naming them.

   `.text-muted` is left alone on purpose: in these tables it marks an ABSENT value - the dash of a
   feed that has counted nothing, the zero of one that has stored nothing - and a quiet nothing is
   the one thing on the row that should not draw the eye. */
.data-table .text-secondary {
  color: var(--theme-foreground-high-contrast);
}

.divider {
  height: 1px;
  margin: 0 var(--size-inset);
  border: 0;
  background: var(--theme-border-low-contrast);
}

.hidden-section {
  display: none;
}

@media (min-width: 600px) {
  .form-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .form-col.flex-2 {
    grid-column: span 2;
  }

  .detail-table th,
  .detail-table td {
    display: table-cell;
    padding: var(--size-2x) 0;
  }

  .detail-table th {
    width: 11rem;
    padding-right: var(--size-2x);
  }
}

@media (min-width: 900px) {
  .stats-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .chart-header--split {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--size-2x);
  }

  .data-table {
    table-layout: auto;
    min-width: 62rem;
    width: max(100%, 62rem);
  }

  .data-table thead {
    display: table-header-group;
  }

  .data-table tbody {
    display: table-row-group;
    padding: 0;
  }

  .data-table tbody tr {
    display: table-row;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  .data-table th {
    padding: var(--size-1x) var(--size-inset);
    border-bottom: 1px solid var(--theme-border-low-contrast);
    color: var(--theme-foreground-low-contrast);
    font-size: var(--font-size-10);
    font-weight: var(--font-weight-regular);
    text-align: left;
    text-transform: uppercase;
    background: var(--theme-background-container);
  }

  .data-table td,
  .data-table td:first-child {
    display: table-cell;
    grid-template-columns: none;
    gap: 0;
    padding: var(--size-inset);
    color: inherit;
    font-size: var(--font-size-30);
    line-height: 1.45;
    border-bottom: 1px solid var(--theme-border-low-contrast);
    vertical-align: middle;
  }

  .data-table td::before {
    display: none;
  }

  .data-table td .cell-secondary {
    margin-top: var(--size-half);
  }

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

  .data-table tbody tr:hover td {
    background: var(--theme-background-container-alt);
  }

  .data-table tbody tr.feed-disabled td {
    background: var(--theme-background-container-alt);
  }

  .action-cell--table {
    flex-wrap: nowrap;
    align-items: center;
    gap: var(--size-half);
  }

  .action-cell--table .btn {
    padding-inline: var(--size-1x);
  }

  .error-panel {
    position: absolute;
    left: auto;
    right: 0;
    top: calc(100% + 0.6rem);
    width: min(38rem, calc(100vw - 6rem));
    z-index: 5;
  }
}

/* ---------------------------------------------------------- phone view -- */

/*
 * Between 600 and 899px a table is still a TABLE: one row per record, scrolled sideways inside
 * its own container, because a tablet has the width to compare records against one column and
 * a row keeps that comparison. Below 600px it does not: the base rules above stack each row into
 * a list, so these rules stop at 600 rather than reaching the phone, where they used to force
 * eleven columns through a 390px slot and show three of them.
 *
 * `.table-card` scrolls (`overflow-x: auto`), so it is what the width overflows into here.
 */
@media (min-width: 600px) and (max-width: 899px) {
  .data-table {
    display: table;
    table-layout: auto;
    min-width: 46rem;
    width: max(100%, 46rem);
  }

  /* Both are single-class selectors, so ORDER decides between them and this has to come second:
     written first, the rule above took the eleven-column table down to 46rem and squeezed its
     title column into four wrapped lines. A wide table is what the sideways scroll is for. */
  .data-table--classic {
    min-width: 66rem;
    width: max(100%, 66rem);
  }

  .data-table thead {
    display: table-header-group;
  }

  .data-table tbody {
    display: table-row-group;
    padding: 0;
  }

  .data-table tbody tr,
  .data-table tbody tr > td:first-child {
    display: table-row;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  .data-table th,
  .data-table td,
  .data-table tbody tr > td:first-child {
    display: table-cell;
    grid-template-columns: none;
    gap: 0;
    padding: var(--size-inset);
    vertical-align: middle;
  }

  .data-table td,
  .data-table tbody tr > td:first-child {
    border-bottom: 1px solid var(--theme-border-low-contrast);
  }

  /* The column header is back, so a value carrying its own name would say it twice. */
  .data-table td::before,
  .data-table tbody tr > td:first-child::before {
    display: none;
  }

  .data-table th {
    padding: var(--size-1x) var(--size-inset);
    border-bottom: 1px solid var(--theme-border-low-contrast);
    color: var(--theme-foreground-low-contrast);
    font-size: var(--font-size-10);
    font-weight: var(--font-weight-regular);
    text-align: left;
    text-transform: uppercase;
  }

  /* Six icon buttons in a row, not a column. `auto` table layout hands the widest column - the
     one holding a source's name - most of the table, which squeezed the action column narrow
     enough for them to wrap one per line and made every row 257px tall to fit a column of them. */
  .data-table .action-cell--table {
    flex-wrap: nowrap;
  }

  /* The outer columns sit on the page's edges, the same as on a wide screen. */
  .data-table th:first-child,
  .data-table td:first-child {
    padding-left: 0;
  }

  .data-table th:last-child,
  .data-table td:last-child {
    padding-right: 0;
  }
}

@media (max-width: 899px) {
  .stats-grid {
    gap: var(--size-1x);
  }

  /* The four counters are a glance, not a section: at a fixed 7.1rem each they pushed the
     list the page is for most of a screen down. */
  .stats-grid--offers .stat-card {
    min-height: 0;
  }

  .pagy {
    gap: var(--size-half);
  }

  .pagy a,
  .pagy span {
    min-width: 2.15rem;
    height: 2.15rem;
    padding: 0 var(--size-1x);
    font-size: var(--font-size-10);
  }
}

/*
 * The filter bar is laid out per row rather than per control below 600px, which is where its
 * five controls stop fitting on two lines. A tablet is left on the plain wrap above, because
 * half-width fields at 760px are a gap, not a layout.
 */
@media (max-width: 599px) {
  .topbar {
    gap: var(--size-1x);
    padding: var(--size-1x) var(--size-1x);
  }

  .topbar__menu {
    width: 2.4rem;
    height: 2.6rem;
  }

  .filter-bar {
    gap: var(--size-inset);
  }

  /* The search box takes a row of its own; the stated narrowings are a sentence and wrap as one. */
  .filter-bar__search {
    flex: 1 1 100%;
    max-width: none;
  }

  .filter-bar__actions {
    flex: 1 1 100%;
  }

  .filter-bar__actions .btn {
    flex: 1 1 0;
  }

  /* Nothing on a phone scrolls sideways: the rows are stacked above, so the wrapper has nothing
     to clip and a clipping box would only cut the hover fill and the error panel. */
  .table-card {
    overflow: visible;
  }

  /* A screen name wraps here. It is one line on a wide screen by rule, but at 390px "Senior Ruby
     on Rails Developer (remote, EU time zone)" on one line is the whole page scrolling sideways. */
  .page-title {
    flex: 1 1 auto;
    min-width: 0;
    white-space: normal;
    overflow-wrap: anywhere;
    font-size: var(--font-size-50);
  }

  /* The screen's actions are ONE row under the title, on every screen: the buttons share the
     width when they fit and the row scrolls sideways when they do not, instead of wrapping into a
     ragged block of two and one. The row comes before the narrowings, which keep their order: 1. */
  .topbar .page-header__actions {
    flex: 1 1 100%;
    flex-wrap: nowrap;
    margin-left: 0;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .topbar .page-header__actions::-webkit-scrollbar {
    display: none;
  }

  .topbar .page-header__actions .btn,
  .topbar .page-header__actions .action-form {
    flex: 1 0 auto;
    padding-inline: var(--size-inset);
  }

  .topbar .page-header__actions .action-form {
    padding-inline: 0;
  }

  .topbar .page-header__actions .action-form .btn {
    width: 100%;
  }

  /* The way back is an icon with the name it leads to cut short, so the actions of the screen keep
     the row. It is first, where a phone puts a back control. */
  .topbar .page-header__actions .btn--back {
    flex: 0 1 auto;
  }

  .topbar .page-header__actions .btn--back span {
    display: block;
    max-width: 7rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The sentence explaining the model is read once, and on a phone it is a screen the list has to
     wait behind; the tab strip under it names the same parts. */
  .page-description--fold {
    display: none;
  }

  .fact-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--size-inset) var(--size-2x);
  }

  /* The four counters are one row of four numbers: a 12px name over a 20px figure, no frame, no
     note. Two boxed cards per row took the whole first screen before the list. */
  .stats-grid,
  .stats-grid--offers {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--size-1x);
    margin-bottom: 0;
  }

  .stat-card,
  .stats-grid--offers .stat-card {
    min-height: 0;
    padding: 0;
    gap: 0;
    border: 0;
    border-radius: 0;
  }

  .stat-label {
    font-size: var(--font-size-10);
    text-transform: none;
  }

  .stat-value,
  .stats-grid--offers .stat-value {
    margin-top: var(--size-half);
    font-size: var(--font-size-50);
  }

  .stat-note,
  .stats-grid--offers .stat-note {
    display: none;
  }

  /* The eleven-column source list keeps the seven cells that answer a phone's question - what
     it is, how it is fetched, where it stores, how much, whether it is well, when it last ran,
     and what can be done about it. The rest stay in the markup for the wider screens. */
  .data-table td.data-table__cell--wide {
    display: none;
  }

  /* A cell holding a short stack of values reads as one line here, the values following each
     other, rather than as a column indented past the label. */
  .data-table td .stack-sm {
    display: block;
  }

  /* On a phone a row action is a button, not a word that takes a border under a pointer it does
     not have, so the border is drawn from the start. */
  .action-cell--table {
    gap: var(--size-half);
  }

  .action-cell--table .btn {
    border-color: var(--theme-border-low-contrast);
  }
}

/* The weekly chart of a collection is closed until it is asked for, at EVERY width: the item
   list is what the tab is for and the curve is a question asked now and then. The summary is one
   more rule in the list's own rhythm, with the open state named at its end. */
.chart-fold > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-1x);
  padding: var(--size-inset) 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
  list-style: none;
  cursor: pointer;
}

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

.chart-fold > summary::after {
  content: "Show";
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
}

.chart-fold[open] > summary::after {
  content: "Hide";
}

.chart-fold .chart-card {
  margin: var(--size-2x) 0 0;
}

@media (min-width: 1200px) {


  .filter-form {
    grid-template-columns: repeat(2, minmax(0, 1fr)) auto;
    align-items: stretch;
  }

  .filter-form__field {
    min-height: 100%;
  }

  .filter-form__actions {
    align-self: end;
    justify-content: flex-end;
  }

  .details-grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  }

  .details-grid--equal {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Two pane shell. The browser action plan editor (E1T26) and the plan history page mount it;
   the extraction plan editor has a shell of its own further down, because its header carries
   profile tabs, the snapshot every number is computed from and the save button, and teaching
   those to the shared shell would have put plan vocabulary into its other caller. */
.two-pane__panes { display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr); gap: var(--size-3x); }
.two-pane__pane { min-width: 0; background: var(--theme-background-container, var(--theme-background-container)); border: 1px solid var(--theme-border-low-contrast); border-radius: var(--radius); padding: var(--size-2x); }
.two-pane__pane-title { font-size: var(--font-size-30); margin-bottom: var(--size-inset); }
@media (max-width: 899px) { .two-pane__panes { grid-template-columns: minmax(0, 1fr); } }

/* Shared with the browser action plan editor's trace table, which draws its own rows out of
   these three. Anything the extraction editor changes about them is scoped under .plan-step or
   .plan-step__readout below, so restyling one screen cannot move the other. */
.plan-step__operation { font-weight: var(--font-weight-medium); }
.plan-step__value-text { display: block; overflow-wrap: anywhere; font-size: var(--font-size-10); }
.plan-step__badge { font-size: var(--font-size-10); }
.plan-step__badge--stopped { color: var(--theme-foreground-error); font-weight: var(--font-weight-medium); }
.plan-pipeline__add { display: flex; gap: var(--size-1x); align-items: center; }
.plan-arg { display: flex; flex-direction: column; font-size: var(--font-size-10); gap: var(--size-half); }
.plan-arg__input { min-width: 12rem; }

.plan-arg input[type="text"],
.plan-arg input[type="number"],
.plan-arg select,
.plan-pipeline__add select {
  min-height: var(--size-5x);
  padding: 0 var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  background: var(--theme-background-container);
  color: var(--theme-foreground-high-contrast);
  font-family: inherit;
  font-size: var(--font-size-30);
}

.plan-arg input[type="text"]:focus-visible,
.plan-arg input[type="number"]:focus-visible,
.plan-arg select:focus-visible,
.plan-pipeline__add select:focus-visible {
  outline: 2px solid var(--theme-accent);
  outline-offset: 0;
  border-color: var(--theme-accent);
}

.plan-arg input[type="checkbox"] {
  position: relative;
  width: calc(var(--size-half) * 4.5);
  height: calc(var(--size-half) * 4.5);
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--theme-border-high-contrast);
  border-radius: 3px;
  background: var(--theme-background-container);
  cursor: pointer;
}

.plan-arg input[type="checkbox"]:checked {
  border-color: var(--theme-foreground-high-contrast);
  background: var(--theme-foreground-high-contrast);
}

.plan-arg input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  left: 5px;
  top: 1px;
  width: 5px;
  height: 10px;
  border: solid var(--theme-foreground-inverse);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}

/* ------------------------------------------------ extraction plan editor (E3T33 redesign) -- */

/* One vocabulary of state for the whole screen, in the same three words the step readouts use.
   `stopped` is the only red on the page, so a broken chain is the thing the eye lands on. */
.plan-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--size-half);
  padding: 0;
  border-radius: 0;
  background: none;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-regular);
  white-space: nowrap;
}

.plan-badge::before {
  content: "";
  width: var(--size-half);
  height: var(--size-half);
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--theme-border-high-contrast);
}


.plan-badge--complete::before { background: var(--theme-foreground-success); }
.plan-badge--partial::before { background: var(--theme-foreground-caution); }
.plan-badge--stopped::before { background: var(--theme-foreground-error); }

.plan-coverage--complete { --plan-state: var(--theme-foreground-success); }
.plan-coverage--partial { --plan-state: var(--theme-foreground-caution); }
.plan-coverage--stopped { --plan-state: var(--theme-foreground-error); }

/* The bar is an svg because its length is a number the server computed, and a presentation
   attribute carries a number without an inline style; the offers chart is drawn the same way. */
.plan-bar {
  display: block;
  width: 5.25rem;
  height: 0.375rem;
  flex: 0 0 auto;
}

.plan-bar__track { fill: var(--theme-border-low-contrast); }
.plan-bar__fill { fill: var(--plan-state, var(--theme-foreground-low-contrast)); }

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

.plan-editor__header {
  position: sticky;
  top: var(--topbar-height);
  z-index: 5;
  padding: var(--size-half) 0 0;
  background: var(--theme-background-base);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.plan-editor__masthead {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--size-3x);
  flex-wrap: wrap;
}

.plan-editor__identity {
  display: flex;
  flex-direction: column;
  gap: var(--size-half);
  min-width: 0;
}

.plan-editor__breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-regular);
  text-transform: none;
  color: var(--theme-foreground-low-contrast);
}

.plan-editor__crumb { color: inherit; }
.plan-editor__crumb--feed { font-family: var(--font-mono); text-transform: none; }
.plan-editor__crumb--current { color: var(--theme-foreground-high-contrast); }

.plan-editor__crumb-arrow {
  width: 0.85rem;
  height: 0.85rem;
  color: var(--theme-foreground-low-contrast);
}

.plan-editor__heading {
  margin: 0;
  font-size: var(--font-size-60);
  font-weight: var(--font-weight-medium);
}

.plan-editor__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.plan-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  padding: var(--size-1x) var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  background: var(--theme-background-container);
}

.plan-chip__icon {
  width: 0.95rem;
  height: 0.95rem;
  color: var(--theme-foreground-low-contrast);
}

.plan-chip__text {
  font-family: var(--font-mono);
  font-size: var(--font-size-10);
  color: var(--theme-foreground-medium-contrast);
}

.plan-editor__tabs {
  display: flex;
  align-items: center;
  gap: var(--size-half);
  margin-top: var(--size-inset);
}

.plan-editor__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  min-height: 2.5rem;
  padding: 0 var(--size-2x);
  border-bottom: 2px solid transparent;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.plan-editor__tab:hover { color: var(--theme-foreground-high-contrast); }

.plan-editor__tab--current {
  border-bottom-color: var(--theme-accent);
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

/* --------------------------------------------------------------------------------- panes -- */

.plan-editor__panes {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 29rem;
  align-items: start;
  gap: var(--size-2x);
  padding: var(--size-2x) 0 var(--size-4x);
}

.plan-editor__pane { min-width: 0; }

.plan-editor__pane--left {
  display: flex;
  flex-direction: column;
  gap: var(--size-2x);
}

.plan-editor__pane--right {
  position: sticky;
  top: calc(var(--topbar-height) + var(--size-8x));
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
}

.plan-editor__chain {
  display: flex;
  flex-direction: column;
  gap: var(--size-2x);
}

@media (max-width: 1199px) {
  .plan-editor__panes { grid-template-columns: minmax(0, 1fr); }
  .plan-editor__pane--right { position: static; }
}

/* ----------------------------------------------------------------------- pipeline as card -- */

.plan-pipeline {
  padding: 0;
  overflow: hidden;
}

.plan-pipeline__header {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-2x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.plan-pipeline__icon {
  width: 1rem;
  height: 1rem;
  color: var(--theme-foreground-low-contrast);
}

.plan-pipeline__title {
  margin: 0;
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.plan-pipeline__title--field {
  font-family: var(--font-mono);
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.plan-pipeline__kind {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-coverage {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  margin-left: auto;
}

.plan-pipeline .plan-pipeline__empty {
  margin: 0;
  padding: var(--size-2x) var(--size-2x);
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-pipeline__steps {
  list-style: none;
  margin: 0;
  padding: var(--size-1x) 0;
}

/* A card whose chain stopped is the one red thing on the screen. The state is read off the
   readout, which is the element the live recompute replaces: a class written onto the card
   itself would keep the answer the page was drawn with while the values under it moved on. */
.plan-pipeline:has(.plan-step__readout--stopped) {
  border-color: var(--theme-border-low-contrast);
}

.plan-pipeline:has(.plan-step__readout--stopped) .plan-pipeline__header {
  background: none;
}

.plan-pipeline__add {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-2x);
  border-top: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container-alt);
}

.plan-pipeline__operation {
  width: 14rem;
  max-width: 100%;
  min-height: 2.15rem;
  padding: var(--size-half) var(--size-1x);
  border-radius: var(--radius);
  font-size: var(--font-size-10);
}

.plan-pipeline__add-button {
  border: 1px dashed var(--theme-border-high-contrast);
  background: var(--theme-background-container);
  color: var(--theme-foreground-medium-contrast);
}

.plan-pipeline__add-button:hover {
  background: var(--theme-background-container-alt);
  color: var(--theme-accent);
}

.plan-pipeline__add-icon {
  width: 0.85rem;
  height: 0.85rem;
}

/* ------------------------------------------------------------------- one step on the rail -- */

.plan-step {
  display: flex;
  gap: var(--size-inset);
  padding: var(--size-1x) var(--size-2x);
  margin: 0;
}

.plan-step__rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex: 0 0 auto;
  padding-top: var(--size-half);
}

.plan-step__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

.plan-step__rail-line {
  width: 2px;
  flex: 1 1 auto;
  min-height: 1rem;
  margin-top: var(--size-half);
  background: var(--theme-border-low-contrast);
}

/* Same reason as the card tint: the colour of the rail is what the recompute just said, and the
   only element carrying that answer is the readout the recompute replaced. */
.plan-step:has(.plan-step__readout--complete) .plan-step__number { background: var(--theme-background-success-subtle); color: var(--theme-foreground-success); }
.plan-step:has(.plan-step__readout--partial) .plan-step__number { background: var(--theme-background-caution-subtle); color: var(--theme-foreground-caution); }
.plan-step:has(.plan-step__readout--stopped) .plan-step__number { background: var(--theme-background-error-subtle); color: var(--theme-foreground-error); }

.plan-step__body {
  flex: 1 1 auto;
  min-width: 0;
}

.plan-step__head {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  flex-wrap: wrap;
  justify-content: flex-start;
}

.plan-step .plan-step__operation {
  padding: var(--size-half) var(--size-1x);
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
  color: var(--theme-accent);
  font-family: var(--font-mono);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

.plan-step__arguments {
  display: flex;
  flex: 1 1 14rem;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-1x);
  min-width: 0;
  margin: 0;
}

.plan-step__no-arguments {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-step__buttons {
  display: flex;
  gap: var(--size-1x);
  margin-left: auto;
}

.plan-step__icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.35rem;
  height: 2.35rem;
  flex: 0 0 auto;
  padding: 0;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
  color: var(--theme-foreground-medium-contrast);
  cursor: pointer;
}

.plan-step__icon-button:hover {
  background: var(--theme-background-accent-subtle);
  border-color: var(--theme-border-high-contrast);
  color: var(--theme-accent);
}

.plan-step__remove:hover {
  background: var(--theme-background-error-subtle);
  border-color: var(--theme-foreground-error);
  color: var(--theme-foreground-error);
}

.plan-step__button-icon {
  width: 1rem;
  height: 1rem;
}

/* -------------------------------------------------------------- one argument, in the line -- */

.plan-step-arg {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
  min-width: 0;
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-step-arg--wide { flex: 1 1 13rem; }

/* Collapsed it is a chip in the line; opened it takes the line, because what it opens onto is a
   list of selectors rather than a word. */
.plan-step-arg--stowed { flex: 0 0 auto; display: block; }
.plan-step-arg--stowed[open] { flex: 1 0 100%; }

.plan-step-arg__stow {
  padding: var(--size-half) var(--size-1x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  cursor: pointer;
}

.plan-step-arg--stowed[open] .plan-step-arg__stow { margin-bottom: var(--size-half); }

.plan-step-arg__name {
  white-space: nowrap;
}

.plan-step-arg__input {
  min-height: 2.35rem;
  padding: var(--size-half) var(--size-inset);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--font-size-20);
}

.plan-step-arg__input--narrow { width: 5.5rem; min-width: 0; }
textarea.plan-step-arg__input { min-height: 3.5rem; }
/* An argument with a declared domain is a select, and a select already sizes to the longest of
   its handful of words, so it takes its own width rather than the 13rem a free-text input claims. */
select.plan-step-arg__input { width: auto; }

/* A boolean argument as a chip rather than a bare checkbox: the box itself is what submits, so
   it stays in the form and is only taken out of sight, and the chip it drives says the argument
   name whether it is on or off. */
.plan-step-arg--toggle { cursor: pointer; }

.plan-step-arg__check {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.plan-step-arg__toggle-face {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  height: 2.35rem;
  padding: 0 var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

.plan-step-arg__toggle-icon {
  width: 0.85rem;
  height: 0.85rem;
  opacity: 0.25;
}

.plan-step-arg__check:checked + .plan-step-arg__toggle-face {
  border-color: transparent;
  background: var(--theme-background-accent-subtle);
  color: var(--theme-accent);
}

.plan-step-arg__check:checked + .plan-step-arg__toggle-face .plan-step-arg__toggle-icon { opacity: 1; }

/* The box that carries the focus is out of sight, so the chip it drives wears the ring: the same
   2px accent outline everything else interactive gets. */
.plan-step-arg__check:focus-visible + .plan-step-arg__toggle-face {
  outline: 2px solid var(--theme-accent);
  outline-offset: 2px;
  border-color: var(--theme-accent);
}

/* ------------------------------------------------------- the value the step just produced -- */

/* The state colour sits on the readout, not on the step, because the readout is what the live
   recompute replaces; on the step it would keep the first answer's colour for ever. */
.plan-step__readout {
  margin-top: var(--size-1x);
  padding: var(--size-1x) var(--size-inset);
  border-left: 3px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
}

.plan-step__readout--complete { border-left-color: var(--theme-foreground-success); }
.plan-step__readout--partial { border-left-color: var(--theme-foreground-caution); background: var(--theme-background-caution-subtle); }
.plan-step__readout--stopped { border-left-color: var(--theme-foreground-error); background: var(--theme-background-error-subtle); }

.plan-step__readout .plan-step__value-text {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-medium-contrast);
}

.plan-step__state { margin: var(--size-half) 0 0; }

.plan-step__readout .plan-step__badge {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-step__readout .plan-step__badge--stopped {
  color: var(--theme-foreground-error);
  font-weight: var(--font-weight-medium);
}

/* -------------------------------------------------------------------- why this change, and
   the history it is written into -- */

.plan-note { padding: var(--size-2x) var(--size-2x); }

.plan-note__label {
  display: flex;
  flex-direction: column;
  gap: var(--size-half);
}

.plan-note__title {
  font-size: var(--font-size-20);
  font-weight: var(--font-weight-medium);
}

.plan-note__hint {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-versions { padding: var(--size-2x) var(--size-2x); }

.plan-versions__head {
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  margin-bottom: var(--size-inset);
}

.plan-versions__title {
  margin: 0;
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
  color: var(--theme-foreground-low-contrast);
}

.plan-versions__rule {
  height: 1px;
  flex: 1 1 auto;
  background: var(--theme-border-low-contrast);
}

.plan-versions__live { font-size: var(--font-size-10); margin: 0 0 var(--size-1x); color: var(--theme-foreground-medium-contrast); }
.plan-versions__empty { font-size: var(--font-size-10); margin: 0; color: var(--theme-foreground-low-contrast); }

.plan-versions__compare {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--size-1x);
  margin-bottom: var(--size-inset);
}

.plan-versions__pick { display: flex; flex-direction: column; font-size: var(--font-size-10); gap: var(--size-half); color: var(--theme-foreground-low-contrast); }

.plan-versions__select {
  width: auto;
  max-width: 18rem;
  min-height: 2.15rem;
  padding: var(--size-half) var(--size-1x);
  border-radius: var(--radius);
  font-size: var(--font-size-10);
}

.plan-versions__timeline {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
  margin: 0;
  padding: 0;
}

.plan-version {
  display: flex;
  align-items: flex-start;
  gap: var(--size-inset);
  padding: var(--size-1x) var(--size-inset);
  border-radius: var(--radius-lg);
  background: var(--theme-background-container-alt);
}

.plan-version__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
}

.plan-version--live .plan-version__mark { background: var(--theme-background-success-subtle); color: var(--theme-foreground-success); }

.plan-version__mark-icon { width: 0.85rem; height: 0.85rem; }

.plan-version__body { flex: 1 1 auto; min-width: 0; }

.plan-version__line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.plan-version__number { font-size: var(--font-size-20); }
.plan-version__meta { font-size: var(--font-size-10); color: var(--theme-foreground-low-contrast); }
.plan-version__note { margin: var(--size-half) 0 0; font-size: var(--font-size-10); color: var(--theme-foreground-medium-contrast); }
.plan-version__no-note { color: var(--theme-foreground-caution); font-style: italic; }
.plan-version__restore-form { margin: 0; }

.plan-diff { margin-top: var(--size-2x); border-top: 1px solid var(--theme-border-low-contrast); padding-top: var(--size-inset); }
.plan-diff__title { font-size: var(--font-size-30); margin-bottom: var(--size-1x); }
.plan-diff__pipeline { margin-bottom: var(--size-inset); }
.plan-diff__pipeline-title { font-size: var(--font-size-20); margin-bottom: var(--size-half); }
.plan-diff__step { border-left: 3px solid var(--theme-border-low-contrast); padding: var(--size-half) var(--size-1x); margin: var(--size-half) 0; font-size: var(--font-size-10); }
.plan-diff__step--added { border-left-color: var(--theme-foreground-success); }
.plan-diff__step--removed { border-left-color: var(--theme-foreground-error); }
.plan-diff__step--changed { border-left-color: var(--theme-foreground-caution); }
.plan-diff__status { font-size: var(--font-size-10); color: var(--theme-foreground-low-contrast); margin-left: var(--size-half); }
.plan-diff__argument { display: flex; flex-wrap: wrap; gap: var(--size-1x); font-size: var(--font-size-10); padding-left: var(--size-inset); overflow-wrap: anywhere; }
.plan-diff__argument-name { font-weight: var(--font-weight-medium); }
.plan-diff__before { color: var(--theme-foreground-error); }
.plan-diff__after { color: var(--theme-foreground-success); }

/* ------------------------------------- the empty state a profile with no plan opens on -- */

.plan-editor__empty {
  display: flex;
  flex-direction: column;
  gap: var(--size-2x);
  max-width: 44rem;
  margin: var(--size-5x) auto;
  padding-bottom: var(--size-4x);
}

.plan-empty {
  padding: var(--size-5x) var(--size-4x);
  text-align: center;
}

.plan-empty__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 999px;
  background: var(--theme-background-caution-subtle);
  color: var(--theme-foreground-caution);
}

.plan-empty__icon { width: 1.5rem; height: 1.5rem; }

.plan-empty__title {
  margin: var(--size-2x) 0 var(--size-1x);
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
}

.plan-empty__body {
  margin: 0 auto var(--size-3x);
  max-width: 28rem;
  font-size: var(--font-size-20);
  line-height: 1.6;
  color: var(--theme-foreground-low-contrast);
}

.plan-empty__actions {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

/* ------------------------------------------------- right pane: the result, and the picker -- */

/* The mode lives on .plan-right, which the live recompute never replaces, so switching modes
   cannot be undone by a Turbo Stream. */
.plan-right[data-mode="result"] .plan-picker { display: none; }
.plan-right[data-mode="pick"] .plan-result { display: none; }

.plan-right {
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
  min-width: 0;
}

.plan-right__modes {
  display: inline-flex;
  align-self: flex-start;
  padding: var(--size-half);
  border-radius: var(--radius);
  border: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container-alt);
}

.plan-right__mode {
  min-height: 2rem;
  border: 0;
  background: transparent;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.plan-right__mode:hover { color: var(--theme-foreground-high-contrast); }

.plan-right[data-mode="result"] .plan-right__mode--result,
.plan-right[data-mode="pick"] .plan-right__mode--pick {
  background: var(--theme-background-container);
  color: var(--theme-accent);
  box-shadow: var(--shadow-sm);
}

.plan-result,
.plan-picker {
  background: var(--theme-background-container);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.plan-result__warning,
.plan-result__reasons,
.plan-result__reason {
  margin: 0;
  padding: var(--size-inset) var(--size-2x);
  font-size: var(--font-size-10);
}

.plan-result__warning { color: var(--theme-foreground-caution); }
.plan-result__reasons { list-style: none; }
.plan-result__reasons .plan-result__reason { padding: var(--size-half) 0; }
.plan-result__reason { color: var(--theme-foreground-error); }

.plan-result__fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-inset) var(--size-2x);
  padding: var(--size-2x) var(--size-2x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.plan-result__count {
  flex: 1 0 100%;
  margin: 0;
  font-size: var(--font-size-10);
  color: var(--theme-foreground-medium-contrast);
}

.plan-result__field {
  display: flex;
  flex-direction: column;
  flex: 1 1 7rem;
  gap: var(--size-half);
  min-width: 0;
}

.plan-result__field-name {
  font-family: var(--font-mono);
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
  overflow-wrap: anywhere;
}

.plan-result__field .plan-bar { width: 100%; }

.plan-result__pair {
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  color: var(--plan-state, var(--theme-foreground-medium-contrast));
}

.plan-result__items {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 32rem;
  overflow-y: auto;
}

.plan-result__item {
  display: flex;
  align-items: flex-start;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-2x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.plan-result__item-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25rem;
  height: 1.25rem;
  flex: 0 0 auto;
  border-radius: 999px;
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
}

.plan-result__cells {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  gap: var(--size-half) var(--size-inset);
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--font-size-10);
}

.plan-result__cell-name { color: var(--theme-foreground-low-contrast); overflow-wrap: anywhere; }
.plan-result__cell-value { color: var(--theme-foreground-high-contrast); overflow-wrap: anywhere; }
.plan-result__cell-value--missing { color: var(--theme-foreground-caution); }

.plan-result__note {
  margin: 0;
  padding: var(--size-inset) var(--size-2x);
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.plan-picker__banner {
  display: flex;
  align-items: flex-start;
  gap: var(--size-1x);
  padding: var(--size-inset) var(--size-2x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container-alt);
}

.plan-picker__banner-icon { width: 0.95rem; height: 0.95rem; flex: 0 0 auto; color: var(--theme-accent); }

.plan-picker__status,
.plan-picker__empty,
.plan-picker__warning {
  margin: 0;
  font-size: var(--font-size-10);
  color: var(--theme-foreground-medium-contrast);
}

.plan-picker__warning { padding: var(--size-2x) var(--size-2x); color: var(--theme-foreground-caution); }
.plan-picker__empty { padding: var(--size-1x) 0; }
.plan-picker__form { margin: 0; }

.plan-picker__frame {
  display: block;
  width: 100%;
  height: 22rem;
  border: 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
}

.plan-picker__candidates { padding: var(--size-inset) var(--size-2x); }

.plan-picker__group {
  margin: var(--size-1x) 0 var(--size-half);
  font-size: var(--font-size-10);
  font-weight: var(--font-weight-medium);
  text-transform: uppercase;
  color: var(--theme-foreground-low-contrast);
}

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

.plan-candidate {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-1x);
  padding: var(--size-1x) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
  font-size: var(--font-size-10);
}

.plan-candidate:first-child { border-top: 0; }

.plan-candidate__selector {
  flex: 1 1 100%;
  overflow-wrap: anywhere;
  font-family: var(--font-mono);
  font-weight: var(--font-weight-medium);
  color: var(--theme-foreground-high-contrast);
}

.plan-candidate__reason { color: var(--theme-foreground-low-contrast); }
.plan-candidate__pair { margin-left: auto; color: var(--theme-foreground-medium-contrast); }

/* Browser action plan editor (E1T26): the same two pane shell, with the rules on the left and
   one paid render's trace on the right. The cost block is styled to be read rather than
   skipped, because a person who does not see what a run spends clicks it again. */
.plan-rule { border: 1px solid var(--theme-border-low-contrast); border-radius: var(--radius); padding: var(--size-1x) var(--size-inset); margin: var(--size-1x) 0; background: var(--theme-background-container, var(--theme-background-container)); }
.plan-rule[draggable="true"] { cursor: grab; }
.plan-rule__handle { color: var(--theme-foreground-low-contrast); margin-right: var(--size-half); }
.plan-cost {
  margin: var(--size-3x) 0;
  padding: 0 0 0 var(--size-inset);
  border: 0;
  border-left: 2px solid var(--theme-foreground-caution);
  background: none;
}
.plan-cost__title { font-size: var(--font-size-20); font-weight: var(--font-weight-medium); margin: 0 0 var(--size-half); }
.plan-cost__list { font-size: var(--font-size-10); margin: 0; padding-left: var(--size-2x); }
.plan-refusal {
  padding: 0 0 0 var(--size-inset);
  border-left: 2px solid var(--theme-foreground-error);
  background: none;
  font-size: var(--font-size-10);
}
.plan-partial { border-left: 3px solid var(--theme-foreground-caution); background: var(--theme-background-caution-subtle); padding: var(--size-1x) var(--size-inset); font-size: var(--font-size-20); }
.plan-snapshot-link { font-size: var(--font-size-10); margin: var(--size-1x) 0; }
.plan-trace { width: 100%; border-collapse: collapse; font-size: var(--font-size-10); }
.plan-trace th, .plan-trace td { text-align: left; vertical-align: top; padding: var(--size-half); border-bottom: 1px solid var(--theme-border-low-contrast); }
.plan-trace__rule { font-size: var(--font-size-10); color: var(--theme-foreground-medium-contrast); margin-left: var(--size-half); }
.plan-trace__shot { width: 12rem; max-width: 100%; border-radius: var(--radius-sm); }

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

/*
 * Somebody who has asked their system for less movement has usually asked because movement makes
 * them ill, so this turns the whole of it off rather than shortening it: every transition and
 * every animation in the file, in one place, so a component added later cannot forget. The
 * durations are near-zero rather than zero so a transitionend handler still fires.
 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/*
 * The declared fields of an item type, one input row per field. A grid rather than a table,
 * because a table inside the form card clipped its last two columns at the card's measure.
 */
.field-rows {
  display: grid;
  gap: var(--size-half);
}

.field-rows__head,
.field-rows__row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) 5rem 5rem;
  gap: var(--size-half);
  align-items: center;
}

.field-rows__head {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--theme-foreground-medium-contrast);
}

.field-rows__check {
  display: flex;
  justify-content: center;
}

@media (max-width: 720px) {
  .field-rows__head {
    display: none;
  }

  .field-rows__row {
    grid-template-columns: 1fr 1fr;
    padding-bottom: var(--size-inset);
    border-bottom: 1px solid var(--theme-border-low-contrast);
  }
}

/* ------------------------------------------------------------- wizard -- */
/*
 * The "Add source" wizard: one column, phone first, the same column capped at 560px and
 * centred on a desktop. Four progress segments under a header carrying the back link, a body
 * that scrolls, and the primary action pinned to the bottom of the viewport.
 */
.wizard {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  min-height: calc(100dvh - var(--topbar-height) - var(--size-6x));
}

.wizard__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-2x);
  min-height: var(--size-7x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.wizard__back {
  display: inline-flex;
  align-items: center;
  gap: var(--size-1x);
  color: var(--theme-foreground-high-contrast);
  text-decoration: none;
}

.wizard__back .icon {
  width: 20px;
  height: 20px;
}

.wizard__step,
.wizard__hint,
.wizard__caption {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.wizard__caption {
  text-transform: none;
}

/* A small action written as a link: "None of these", "Add a field from the page". */
.wizard__link {
  padding: var(--size-half) 0;
  color: var(--theme-accent);
  font-size: var(--font-size-10);
  text-decoration: none;
}

.wizard__progress {
  display: flex;
  gap: var(--size-half);
  padding-top: var(--size-inset);
}

.wizard__segment {
  flex: 1 1 0;
  height: 3px;
  border-radius: 2px;
  background: var(--theme-border-low-contrast);
}

.wizard__segment--done {
  background: var(--theme-accent);
}

.wizard__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--size-3x);
  padding: var(--size-3x) 0 var(--size-2x);
}

.wizard__intro,
.wizard__section {
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
}

.wizard__title {
  margin: 0;
  font-size: var(--font-size-60);
  line-height: 1.2;
  font-weight: var(--font-weight-medium);
}

.wizard__lede {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
}

.wizard__eyebrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  text-transform: uppercase;
}

.wizard__foot {
  position: sticky;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
  padding: var(--size-inset) 0 var(--size-3x);
  border-top: 1px solid var(--theme-border-low-contrast);
  background: var(--theme-background-container);
}

.wizard__foot .btn {
  min-height: var(--size-6x);
  width: 100%;
  font-size: 15px;
}

.wizard__foot-note {
  text-align: center;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* The address input is the one control on the first screen and is sized like one. */
.wizard__address {
  min-height: var(--size-6x);
  font-size: 16px;
}

.wizard-card {
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
  padding: var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
}

.wizard-card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-1x);
}

.wizard-card__title {
  font-weight: var(--font-weight-medium);
}

.wizard-card__meta {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.wizard-verdict {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--font-size-10);
  color: var(--theme-foreground-success);
}

.wizard-verdict--blocked {
  color: var(--theme-foreground-error);
}

.wizard-verdict::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
}

.wizard-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-1x);
}

.wizard-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--theme-background-container-alt);
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

.wizard-chip--accent {
  background: var(--theme-background-accent-subtle);
  color: var(--theme-accent);
}

/* A collection chip is a link that reloads the screen with that collection chosen. */
.wizard-pick {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: var(--size-5x);
  padding: 0 14px;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  color: var(--theme-foreground-high-contrast);
  text-decoration: none;
}

.wizard-chips .wizard-pick,
.wizard-chips .wizard-pick:hover {
  color: var(--theme-foreground-high-contrast);
}

.wizard-pick--selected {
  border-color: var(--theme-border-high-contrast);
  background: var(--theme-background-container-alt);
  font-weight: var(--font-weight-medium);
}

.wizard-pick--new {
  border-style: dashed;
  border-color: var(--theme-border-high-contrast);
  color: var(--theme-foreground-medium-contrast);
}

/* A radio card: the input is kept for the keyboard and the screen reader, the card draws the
   state through :has, so no script is needed to select one. */
.wizard-choices {
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
}

.wizard-choice {
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  padding: var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  color: var(--theme-foreground-high-contrast);
  text-decoration: none;
  cursor: pointer;
}

.wizard-choice:has(:checked),
.wizard-choice--selected {
  border-color: var(--theme-accent);
  background: var(--theme-background-accent-subtle);
}

.wizard-choice__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  opacity: 0;
}

.wizard-choice__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  border: 1px solid var(--theme-border-high-contrast);
  color: var(--theme-foreground-inverse);
}

.wizard-choice__mark .icon {
  display: none;
  width: 12px;
  height: 12px;
}

.wizard-choice:has(:checked) .wizard-choice__mark,
.wizard-choice--selected .wizard-choice__mark {
  border-color: var(--theme-accent);
  background: var(--theme-accent);
}

.wizard-choice:has(:checked) .wizard-choice__mark .icon,
.wizard-choice--selected .wizard-choice__mark .icon {
  display: block;
}

.wizard-choice:has(:focus-visible) {
  outline: 2px solid var(--theme-accent);
  outline-offset: 2px;
}

.wizard-choice__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.wizard-choice__title {
  font-weight: var(--font-weight-medium);
}

.wizard-choice__text {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

.wizard-choice__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-1x);
}

/* The rows of one card's fields, and the rows a feed preview draws. */
.wizard-rows {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
}

.wizard-row {
  display: flex;
  align-items: center;
  gap: var(--size-inset);
  padding: var(--size-inset);
  border-bottom: 1px solid var(--theme-border-low-contrast);
  color: var(--theme-foreground-high-contrast);
  text-decoration: none;
}

.wizard-row:last-child {
  border-bottom: 0;
}

.wizard-row__label {
  flex: 0 0 64px;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

.wizard-row__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wizard-row__value--strong {
  font-weight: var(--font-weight-medium);
}

.wizard-row__value--muted,
.wizard-row__note {
  color: var(--theme-foreground-low-contrast);
}

.wizard-row__note {
  font-size: var(--font-size-10);
  white-space: nowrap;
}

.wizard-row__note--found {
  color: var(--theme-foreground-success);
}

.wizard-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--theme-border-low-contrast);
}

.wizard-dot--found {
  background: var(--theme-foreground-success);
}

.wizard-preview {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.wizard-preview__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.wizard-preview__title {
  font-weight: var(--font-weight-medium);
}

.wizard-preview__text {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.wizard-slider {
  width: 100%;
  accent-color: var(--theme-accent);
}

.wizard-slider__ends {
  display: flex;
  justify-content: space-between;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* Done: the address boxes and the link list. */
.wizard-address {
  display: flex;
  align-items: center;
  gap: var(--size-1x);
}

.wizard-address__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--size-5x);
  padding: 0 var(--size-inset);
  border: 0;
  border-radius: var(--radius-sm);
  background: var(--theme-background-container-alt);
  font-family: var(--font-mono);
  font-size: var(--font-size-10);
  color: var(--theme-foreground-high-contrast);
}

.wizard-address__copy {
  flex: 0 0 auto;
  width: var(--size-5x);
  height: var(--size-5x);
  padding: 0;
}

.wizard-address__copy[data-copied] {
  border-color: var(--theme-foreground-success);
  color: var(--theme-foreground-success);
}

.wizard-links {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.wizard-links__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
  color: var(--theme-foreground-high-contrast);
  text-decoration: none;
}

.wizard-links__item .icon {
  color: var(--theme-foreground-low-contrast);
}

.wizard-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--size-5x);
  height: var(--size-5x);
  border-radius: 50%;
  background: var(--theme-background-success-subtle);
  color: var(--theme-foreground-success);
}

/* The picker, as a sheet over the whole screen. It opens through :target, so a row is a plain
   link to it and the close link is a link back, with no script of its own; the picker's
   frame, form and candidates inside are the plan editor's, rendered unchanged. */
.wizard-sheet {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: none;
  flex-direction: column;
  background: var(--theme-background-container);
}

.wizard-sheet:target {
  display: flex;
}

.wizard-sheet__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-2x);
  min-height: var(--topbar-height);
  padding: 0 var(--size-2x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.wizard-sheet__title {
  margin: 0;
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.wizard-sheet__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
  padding: var(--size-2x);
  overflow: auto;
}

.wizard-sheet .plan-picker {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
}

.wizard-sheet .plan-picker__frame {
  flex: 1 1 auto;
  min-height: 45vh;
}

.wizard-sheet .plan-picker__candidates {
  flex: 0 0 auto;
  max-height: 35vh;
  overflow: auto;
}

/* -------------------------------------------------------------- landing -- */

/* The public front page at `/`, rendered by app/views/landing under its own layout: no rail, no
   topbar. The tokens are the app's; the type scale
   goes above `--font-size-70` because a marketing headline is not an interface heading, and the
   two breakpoints are the stylesheet's own (600 and 900). */
.landing {
  font-size: 16px;
}

.landing h1,
.landing h2,
.landing h3 {
  margin: 0;
  font-weight: var(--font-weight-medium);
}

.landing-main,
.landing-nav,
.landing-footer {
  width: min(100% - var(--size-4x), 1120px);
  margin-inline: auto;
}

.landing-nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-2x);
  min-height: var(--size-8x);
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.landing-nav__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--size-inset);
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

.landing-nav__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: var(--theme-accent);
  color: var(--theme-foreground-inverse);
}

.landing-nav__mark svg {
  width: 18px;
  height: 18px;
}

.landing-nav__links {
  display: flex;
  align-items: center;
  gap: var(--size-4x);
  font-size: var(--font-size-30);
}

.landing-nav__link {
  color: var(--theme-foreground-medium-contrast);
}

/* Sign in is a button, not a link in the run of page anchors: a returning person looks for it
   first and it was the one thing on the bar that read as plain text. */
.landing-nav__signin {
  min-height: var(--size-5x);
  padding: 0 var(--size-2x);
}

.landing-flash {
  width: min(100% - var(--size-4x), 1120px);
  margin: var(--size-2x) auto 0;
  padding: var(--size-inset) var(--size-2x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius);
  background: var(--theme-background-container-alt);
}

.landing-flash--alert {
  background: var(--theme-background-error-subtle);
  color: var(--theme-foreground-error);
}

.landing-hero {
  display: grid;
  grid-template-columns: minmax(0, 11fr) minmax(0, 9fr);
  gap: var(--size-8x);
  align-items: center;
  padding: 96px 0 72px;
}

.landing-hero__copy {
  display: flex;
  flex-direction: column;
  gap: var(--size-3x);
}

.landing-hero__title {
  font-size: 56px;
  line-height: 1.08;
  letter-spacing: -0.01em;
}

.landing-hero__lede {
  margin: 0;
  max-width: 520px;
  color: var(--theme-foreground-medium-contrast);
  font-size: 18px;
}

.landing-hero__audience {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
}

.landing-signup {
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
  max-width: 520px;
}

.landing-signup__row {
  display: flex;
  gap: var(--size-1x);
}

.landing-signup__input {
  flex: 1 1 auto;
  min-height: var(--size-6x);
  padding-inline: var(--size-2x);
  border-color: var(--theme-border-high-contrast);
  font-size: 16px;
}

.landing-signup__submit {
  flex: 0 0 auto;
  min-height: var(--size-6x);
}

.landing-signup__note {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* The example feed: the one card on the page that floats, because it stands for a reader's
   window rather than for a region of this page. */
.landing-feed {
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
  padding: var(--size-3x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-lg);
  box-shadow: 0 10px 30px rgba(17, 20, 24, 0.12);
}

.landing-feed__head,
.landing-feed__item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--size-2x);
}

.landing-feed__head {
  align-items: center;
}

.landing-feed__title,
.landing-feed__name,
.landing-feed__score {
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-medium);
}

.landing-feed__tag,
.landing-feed__meta,
.landing-feed__foot {
  font-size: var(--font-size-10);
  color: var(--theme-foreground-low-contrast);
}

.landing-feed__meta {
  color: var(--theme-foreground-medium-contrast);
}

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

.landing-feed__item {
  padding: var(--size-inset) 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.landing-feed__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.landing-feed__score {
  flex: 0 0 auto;
}

.landing-feed__score--good {
  color: var(--theme-foreground-success);
}

.landing-section {
  display: flex;
  flex-direction: column;
  gap: var(--size-3x);
  padding-bottom: 80px;
}

.landing-section__head {
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
  max-width: 680px;
}

.landing-section__title {
  font-size: 40px;
  line-height: 1.15;
}

.landing-section__intro {
  max-width: 680px;
  margin: calc(var(--size-1x) * -1) 0 0;
}

.landing-kicker {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.landing-muted {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
}

.landing-problem {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--size-6x);
}

.landing-problem__col {
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
}

.landing-problem__title {
  font-size: 28px;
  line-height: 1.25;
}

.landing-grid {
  display: grid;
  gap: var(--size-4x);
  list-style: none;
  margin: 0;
  padding: 0;
}

.landing-grid--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

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

.landing-grid--4 {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.landing-cell {
  display: flex;
  flex-direction: column;
  gap: var(--size-1x);
  padding: var(--size-3x) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.landing-cell__label {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.landing-cell__title {
  font-size: var(--font-size-50);
  line-height: 1.3;
}

.landing-cell__title--sm {
  font-size: 16px;
}

.landing-cta {
  align-self: flex-start;
  min-height: var(--size-6x);
  padding-inline: var(--size-4x);
}

.landing-why {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--size-6x);
  align-items: start;
}

.landing-why__story {
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: 18px;
}

.landing-box {
  display: flex;
  flex-direction: column;
  gap: var(--size-inset);
  padding: var(--size-3x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-lg);
}

.landing-box__title {
  font-weight: var(--font-weight-medium);
}

.landing-box__text {
  font-size: var(--font-size-30);
}

.landing-refund {
  max-width: 680px;
}

.landing-plans {
  gap: var(--size-3x);
}

.landing-plan {
  display: flex;
  flex-direction: column;
  gap: var(--size-2x);
  padding: var(--size-4x);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-lg);
}

.landing-plan--featured {
  border-color: var(--theme-accent);
  background: var(--theme-background-accent-subtle);
}

.landing-plan__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.landing-plan__name {
  font-size: 16px;
}

.landing-plan__badge {
  color: var(--theme-accent);
  font-size: var(--font-size-10);
}

.landing-plan__price {
  font-size: 40px;
  font-weight: var(--font-weight-medium);
  line-height: 1;
}

.landing-plan__period {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-regular);
}

.landing-plan__cta {
  margin-top: auto;
  min-height: 44px;
  text-align: center;
}

.landing-qa {
  margin: 0;
}

.landing-qa--closed {
  border-bottom: 1px solid var(--theme-border-low-contrast);
}

.landing-qa__row {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
  gap: var(--size-4x);
  padding: 20px 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.landing-qa__q {
  font-weight: var(--font-weight-medium);
}

.landing-qa__a {
  margin: 0;
}

.landing-final {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--size-3x);
  padding: 80px var(--size-6x);
  background: var(--theme-background-container-alt);
}

.landing-final__title {
  max-width: 720px;
  font-size: 40px;
  line-height: 1.15;
  text-align: center;
}

.landing-final__form {
  width: min(100%, 520px);
}

.landing-final__ps {
  max-width: 640px;
  margin: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
  text-align: center;
}

.landing-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-2x);
  padding: var(--size-3x) 0;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.landing-footer__links {
  display: flex;
  gap: var(--size-3x);
}

.landing-footer__links a {
  color: var(--theme-foreground-low-contrast);
}

@media (max-width: 899px) {
  .landing-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--size-4x);
    padding: var(--size-6x) 0 var(--size-4x);
  }

  .landing-hero__title {
    font-size: 40px;
  }

  .landing-grid--3,
  .landing-grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .landing-why {
    grid-template-columns: minmax(0, 1fr);
  }

  .landing-section__title,
  .landing-final__title {
    font-size: 32px;
  }
}

@media (max-width: 599px) {
  .landing-nav {
    min-height: var(--size-7x);
  }

  .landing-nav__link {
    display: none;
  }

  .landing-hero {
    padding-top: var(--size-5x);
  }

  .landing-hero__title {
    font-size: 34px;
    line-height: 1.12;
  }

  .landing-hero__lede {
    font-size: 16px;
  }

  .landing-signup__row {
    flex-direction: column;
  }

  .landing-signup__submit,
  .landing-cta,
  .landing-plan__cta,
  .landing-nav__cta {
    width: 100%;
    min-height: var(--size-6x);
  }

  .landing-nav__cta {
    width: auto;
    min-height: 44px;
    padding-inline: var(--size-2x);
  }

  .landing-feed {
    padding: var(--size-2x);
  }

  .landing-problem,
  .landing-grid--2,
  .landing-grid--3,
  .landing-grid--4,
  .landing-qa__row {
    grid-template-columns: minmax(0, 1fr);
  }

  .landing-problem {
    gap: var(--size-4x);
  }

  .landing-problem__title {
    font-size: 22px;
  }

  .landing-section {
    padding-bottom: var(--size-6x);
  }

  .landing-section__title,
  .landing-final__title {
    font-size: 28px;
    line-height: 1.2;
  }

  .landing-grid {
    gap: 0;
  }

  .landing-cell {
    padding: var(--size-2x) 0;
  }

  .landing-cell__title {
    font-size: 16px;
  }

  .landing-qa__row {
    gap: var(--size-1x);
  }

  .landing-final {
    padding: var(--size-6x) var(--size-2x);
  }

  .landing-footer {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* ------------------------------------------------------- collection page -- */
/*
 * One collection: a breadcrumb in the bar, four quiet numbers, a tab strip, and lists drawn as
 * rules rather than boxes. Every colour here is a token above; nothing is filled dark.
 */

/* Where you are, in the bar: "Collections / Ruby jobs", the name at 17px. One link is drawn two
   ways, as the word from 600px up and as an arrow under it, because a phone bar has no room for
   a trail and an arrow is what a phone reads as "back". */
.crumbs {
  display: flex;
  align-items: center;
  gap: var(--size-2x);
  min-width: 0;
}

.crumbs__parent {
  display: inline-flex;
  align-items: center;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-30);
  white-space: nowrap;
}

.crumbs__parent:hover,
.crumbs__parent:focus-visible {
  color: var(--theme-foreground-high-contrast);
}

.crumbs__parent-icon {
  display: none;
}

.crumbs__separator {
  color: var(--theme-border-high-contrast);
}

.crumbs__current {
  margin: 0;
  overflow: hidden;
  font-size: var(--font-size-40);
  font-weight: var(--font-weight-medium);
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.btn-add-source .icon {
  width: 0.875rem;
  height: 0.875rem;
}

/* Four numbers under a 1px rule each, no cards: the label in the quiet ink, the number at 20px,
   and one 12px line under it. `.figure--narrow` is the phone's own "Today" block and is hidden
   here; the phone block below swaps it in. */
.figures {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--size-2x);
  margin: 0;
}

.figure {
  display: flex;
  flex-direction: column;
  gap: var(--size-half);
  min-width: 0;
  padding: var(--size-2x) 0;
  border-top: 1px solid var(--theme-border-low-contrast);
}

.figure__label {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-10);
}

.figure__value {
  margin: 0;
  font-size: var(--font-size-50);
  font-weight: var(--font-weight-medium);
  line-height: 1.4;
}

/* "no analyzer" is an absence, not a number, so it is not drawn as one. */
.figure__value--none {
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-regular);
  line-height: 2;
}

.figure__note {
  margin: 0;
  overflow: hidden;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.figure--narrow {
  display: none;
}

/* The tab strip: text on one rule, the open tab named by a 2px rule in the medium ink. The
   grey-pill `.tabs` above is what the feeds index still wears; this modifier takes the pill away. */
.tabs--strip {
  gap: var(--size-3x);
  margin: 0;
  padding: 0;
  border-bottom: 1px solid var(--theme-border-low-contrast);
  border-radius: 0;
  background: transparent;
}

.tabs--strip .tab {
  min-height: 0;
  margin-bottom: -1px;
  padding: 10px 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-30);
  font-weight: var(--font-weight-regular);
}

.tabs--strip .tab:hover,
.tabs--strip .tab:focus-visible {
  background: transparent;
  color: var(--theme-foreground-high-contrast);
}

.tabs--strip .tab.active {
  background: transparent;
  border-bottom-color: var(--theme-foreground-medium-contrast);
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

/* The Items tab's narrowings as a row of chips: the search box at 320px, then a select-like chip
   per narrowing, and the count the narrowings leave at the far end. A chip is drawn by its span
   and the select is laid transparently over it, for the reason the filter bar gives. */
.chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-inset);
}

.chips__row {
  display: contents;
}

.chips__search {
  position: relative;
  display: flex;
  align-items: center;
  width: 320px;
  max-width: 100%;
}

.chips__search-icon {
  position: absolute;
  left: var(--size-inset);
  display: inline-flex;
  color: var(--theme-foreground-low-contrast);
  pointer-events: none;
}

.chips__search-icon .icon {
  width: 1rem;
  height: 1rem;
}

.chips__search .form-control {
  padding-left: var(--size-5x);
}

.chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--size-5x);
  padding: 0 var(--size-inset);
  border: 1px solid var(--theme-border-low-contrast);
  border-radius: var(--radius-sm);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-30);
  white-space: nowrap;
  cursor: pointer;
}

/* A chip holding a value: the grey the app uses for a selected thing, never a dark fill. */
.chip--set {
  border-color: var(--theme-border-high-contrast);
  background: var(--theme-background-container-alt);
}

.chip__shown {
  padding-right: 18px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%238b95a3' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right center;
}

.chip__select {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  opacity: 0;
  cursor: pointer;
}

.chip:has(.chip__select:focus-visible) {
  outline: 2px solid var(--theme-accent);
  outline-offset: 2px;
}

.chip--reset {
  color: var(--theme-foreground-medium-contrast);
}

/* The search icon pill is the phone's way into the box and has no place on a wide screen. */
.chip--icon {
  display: none;
  flex: 0 0 auto;
  justify-content: center;
  width: var(--size-5x);
  padding: 0;
}

.chip--icon .icon {
  width: 1rem;
  height: 1rem;
}

.chips__count {
  margin-left: auto;
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

/* A list drawn as rules: from 600px up a real table whose captions are 12px uppercase in the
   quiet ink, whose rows are parted by the 1px rule and nothing else, and whose outer columns
   sit on the page's own edges. Below 600px it is the stacked list every `.data-table` is. */
@media (min-width: 600px) {
  .data-table--list thead {
    display: table-header-group;
  }

  .data-table--list tbody {
    display: table-row-group;
    padding: 0;
  }

  .data-table--list tbody tr {
    display: table-row;
    padding: 0;
    border: 0;
    background: transparent;
  }

  .data-table--list th {
    padding: 0 var(--size-2x) var(--size-1x) 0;
    border-bottom: 1px solid var(--theme-border-low-contrast);
    color: var(--theme-foreground-low-contrast);
    font-size: var(--font-size-10);
    font-weight: var(--font-weight-regular);
    text-align: left;
    text-transform: uppercase;
  }

  .data-table--list td,
  .data-table--list td:first-child {
    display: table-cell;
    padding: 14px var(--size-2x) 14px 0;
    border-bottom: 1px solid var(--theme-border-low-contrast);
    vertical-align: middle;
    color: var(--theme-foreground-medium-contrast);
    font-size: var(--font-size-10);
    line-height: 1.45;
  }

  .data-table--list th:last-child,
  .data-table--list td:last-child {
    padding-right: 0;
  }

  .data-table--list td::before {
    display: none;
  }

  .data-table--list td:first-child,
  .data-table--list td .cell-primary {
    color: var(--theme-foreground-high-contrast);
    font-size: var(--font-size-30);
  }

  .data-table--list td .cell-secondary {
    margin-top: 2px;
  }

  /* The item list's own columns: fixed, so a long description is clipped to its cell rather
     than widening it, and the score, source and time keep the widths the design gives them. */
  .data-table--items {
    table-layout: fixed;
  }

  .data-table--items th:nth-child(2) { width: 30%; }
  .data-table--items th:nth-child(3) { width: 72px; }
  .data-table--items th:nth-child(4) { width: 120px; }
  .data-table--items th:nth-child(5) { width: 140px; }

  .data-table--items .cell-secondary--clip {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .data-table--items td[data-label="Score"] {
    font-size: var(--font-size-30);
  }

  .data-table--items tbody tr:hover td {
    background: var(--theme-background-container-alt);
  }
}

.data-table--items td[data-label="Source"] .cell-code {
  color: var(--theme-foreground-low-contrast);
}

/* A score: 14px in the darkest ink, green once it clears `Item::Listing::HIGH_SCORE`, and
   "Pending" in the quiet ink when nobody has judged the item yet. */
.item-score {
  color: var(--theme-foreground-high-contrast);
  font-weight: var(--font-weight-medium);
}

.item-score--high {
  color: var(--theme-foreground-success);
}

.item-score--pending {
  color: var(--theme-foreground-low-contrast);
  font-weight: var(--font-weight-regular);
}

/* The pagination as one line under the list: what is shown at the left, small numbered squares
   at the right, the current page the one with a border. */
.pagination-wrapper--row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-inset);
}

.pagination-wrapper--row .pagination-info {
  color: var(--theme-foreground-low-contrast);
  font-size: var(--font-size-10);
}

.pagination-wrapper--row .pagy {
  justify-content: flex-end;
}

.pagination-wrapper--row .pagy a,
.pagination-wrapper--row .pagy span {
  min-width: var(--size-4x);
  height: var(--size-4x);
  padding: 0 var(--size-half);
  border-color: transparent;
  color: var(--theme-foreground-medium-contrast);
  font-weight: var(--font-weight-regular);
}

.pagination-wrapper--row .pagy a.current,
.pagination-wrapper--row .pagy span.current {
  border-color: var(--theme-border-low-contrast);
  background: transparent;
  color: var(--theme-foreground-high-contrast);
}

/* The phone artboard: an arrow, the name and a square "+" in the bar; three numbers with no
   rules; the tab strip scrolling sideways; one row of 36px pills with the search behind an icon;
   and one block per item, the score at the title's right. */
@media (max-width: 599px) {
  .crumbs {
    flex: 1 1 auto;
    gap: var(--size-inset);
  }

  .crumbs__parent-text,
  .crumbs__separator,
  .crumbs__settings,
  .btn-add-source__text {
    display: none;
  }

  .crumbs__parent-icon {
    display: inline-flex;
    color: var(--theme-foreground-high-contrast);
  }

  /* The bar's own phone rule hands the actions a row of their own and stretches every button
     across it; the one square "+" stays beside the name instead. */
  .topbar .page-header__actions--crumbs {
    flex: 0 0 auto;
    margin-left: auto;
  }

  .topbar .page-header__actions--crumbs .btn-add-source {
    flex: 0 0 auto;
    width: var(--size-5x);
    min-height: var(--size-5x);
    padding: 0;
  }

  .btn-add-source .icon {
    width: 1rem;
    height: 1rem;
  }

  .figures {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--size-inset);
  }

  .figure {
    gap: 2px;
    padding: 0;
    border-top: 0;
  }

  .figure--wide,
  .figure__note--wide {
    display: none;
  }

  .figure--narrow {
    display: flex;
  }

  .tabs--strip {
    gap: 20px;
  }

  /* The pills in one row that scrolls sideways, the count left to the list's own foot. */
  .chips {
    gap: var(--size-1x);
  }

  .chips__row {
    display: flex;
    flex: 1 1 100%;
    gap: var(--size-1x);
    overflow-x: auto;
    padding-bottom: 2px;
    scrollbar-width: none;
  }

  .chips__row::-webkit-scrollbar {
    display: none;
  }

  .chip {
    flex: 0 0 auto;
    height: 36px;
    border-radius: 18px;
    font-size: var(--font-size-20);
  }

  .chip--icon {
    display: inline-flex;
    width: 36px;
  }

  .chips__count {
    display: none;
  }

  /* Clipped away, still focusable: the icon pill is a label for this field, so pressing it puts
     the focus here, and the box takes a row of its own above the pills while it holds the focus
     or a query. Blurring an empty box clips it again. */
  .chips__search {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  .chips__search--open,
  .chips__search:focus-within {
    position: relative;
    flex: 1 1 100%;
    width: auto;
    height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
  }

  .data-table--items tbody tr {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas:
      "title title score"
      "company company company"
      "source added added";
    align-items: start;
    gap: var(--size-half) var(--size-1x);
  }

  .data-table--items td::before {
    display: none;
  }

  .data-table--items td[data-label="Item"] {
    grid-area: title;
  }

  .data-table--items td[data-label="Item"] .cell-secondary {
    display: none;
  }

  .data-table--items td[data-label="Company"] {
    grid-area: company;
  }

  .data-table--items td[data-label="Score"] {
    grid-area: score;
    justify-self: end;
    font-size: var(--font-size-30);
  }

  .data-table--items td[data-label="Source"] {
    grid-area: source;
  }

  .data-table--items td[data-label="Added"] {
    grid-area: added;
    color: var(--theme-foreground-low-contrast);
  }

  .data-table--items td[data-label="Added"]::before {
    content: "\00b7";
    display: inline;
    color: var(--theme-foreground-low-contrast);
  }

  .pagination-wrapper--row {
    justify-content: center;
  }
}

/* The legal pages at /privacy and /terms: one Markdown file rendered through the landing layout,
   so the only thing missing from the tokens above is prose spacing. `.landing h1, h2, h3` sets
   `margin: 0` for a marketing page whose sections space themselves; a document does not, which
   is what this block puts back. */
.landing-prose {
  padding: var(--size-6x) 0 var(--size-8x);
  max-width: 42rem;
  color: var(--theme-foreground-medium-contrast);
  font-size: var(--font-size-40);
  line-height: 1.65;
}

.landing-prose h1 {
  margin: 0 0 var(--size-2x);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-70);
}

.landing-prose h2 {
  margin: var(--size-6x) 0 var(--size-2x);
  color: var(--theme-foreground-high-contrast);
  font-size: var(--font-size-50);
}

.landing-prose p,
.landing-prose ul,
.landing-prose ol {
  margin: 0 0 var(--size-2x);
}

.landing-prose ul,
.landing-prose ol {
  padding-left: var(--size-3x);
}

.landing-prose li {
  margin-bottom: var(--size-1x);
}

.landing-prose a {
  color: var(--theme-accent);
}
