:root {
  color-scheme: light;
  --ink: #17211d;
  --muted: #68716d;
  --paper: #f5f4f1;
  --panel: rgba(255, 255, 255, 0.68);
  --panel-solid: #fff;
  --line: rgba(23, 33, 29, 0.12);
  --accent: #b8662f;
  --accent-dark: #82471e;
  --green: #3d6f58;
  --blue: #315b7d;
  --red: #9b4d45;
  --shadow: 0 14px 38px rgba(23, 33, 29, 0.06);
  --app-sidebar-width: 224px;
  font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", "Segoe UI", sans-serif;
  font-size: 16px;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  background: var(--paper);
  color: var(--ink);
}

/* Cross-document view transitions live at the end of this file. They became
   possible when the frosted bar left: a snapshot is a flat picture, and an
   element drawing itself from its backdrop lost its frost for the length of
   every animation. Nothing on the page does that any more. */

* {
  box-sizing: border-box;
}

[hidden] {
  display: none;
}

html {
  min-height: 100%;
  background: var(--paper);
}

body {
  min-height: 100vh;
  margin: 0;
  background:
    radial-gradient(circle at 92% -8%, rgba(184, 102, 47, 0.09), transparent 32rem),
    radial-gradient(circle at -8% 92%, rgba(61, 111, 88, 0.055), transparent 28rem),
    var(--paper);
  letter-spacing: -0.006em;
  -webkit-font-smoothing: antialiased;
}

a {
  color: var(--accent-dark);
  text-underline-offset: 0.18em;
}

strong {
  font-weight: 600;
}

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

button,
.primary-button,
.secondary-button {
  min-height: 44px;
}

button {
  cursor: pointer;
}

:focus-visible {
  outline: 2px solid rgba(23, 33, 29, 0.3);
  outline-offset: 2px;
}

.brand-mark,
.auth-brand {
  display: grid;
  place-items: center;
  color: var(--ink);
  background: #efe9dc;
  font-weight: 650;
}

.brand-mark {
  width: 38px;
  height: 38px;
  border-radius: 12px;
}

.auth-brand {
  width: 60px;
  height: 60px;
  margin: 0 auto 22px;
  border-radius: 18px;
  box-shadow: var(--shadow);
  font-size: 1.45rem;
}

.app-navigation-desktop {
  display: grid;
  gap: 4px;
}

.app-nav-item {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 48px;
  border-radius: 14px;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

.app-nav-glyph {
  position: relative;
  display: grid;
  width: 48px;
  height: 48px;
  place-items: center;
  border: 1px solid transparent;
  border-radius: 16px;
  color: #69716d;
  transition:
    color 180ms ease,
    transform 220ms cubic-bezier(0.2, 0.85, 0.25, 1),
    background-color 180ms ease,
    box-shadow 180ms ease;
}

.app-nav-glyph svg {
  position: relative;
  z-index: 1;
  width: 23px;
  height: 23px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.65;
  vector-effect: non-scaling-stroke;
}

.app-sidebar {
  display: none;
}

.menu-signout {
  margin: 0;
}

.mobile-session-panel .menu-signout {
  margin-top: 18px;
}

.mobile-session-panel .secondary-button {
  width: 100%;
}

.app-main {
  position: relative;
  width: min(780px, 100%);
  margin: 0 auto;
  padding:
    max(30px, env(safe-area-inset-top))
    max(18px, env(safe-area-inset-right))
    max(48px, env(safe-area-inset-bottom))
    max(18px, env(safe-area-inset-left));
}

.auth-shell {
  width: min(430px, 100%);
  margin: max(5vh, 28px) auto 0;
  text-align: center;
}

.auth-shell h1,
.page-heading h1 {
  margin: 5px 0 7px;
  font-size: clamp(1.8rem, 4vw, 2.25rem);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.04em;
}

.eyebrow {
  margin: 0;
  color: var(--accent-dark);
  font-size: 0.71rem;
  font-weight: 620;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.muted,
.result-count,
.request-meta,
.request-card time,
.detail-heading time,
.timeline time,
small {
  color: var(--muted);
}

.panel {
  margin: 18px 0;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel);
  box-shadow: var(--shadow);
}

.panel h2 {
  margin: 0 0 18px;
  font-size: 1.08rem;
  font-weight: 600;
}

.panel h3 {
  margin: 24px 0 10px;
  font-size: 1rem;
  font-weight: 600;
}

.form-stack {
  display: grid;
  gap: 17px;
  text-align: left;
}

label {
  display: grid;
  gap: 7px;
  color: var(--ink);
  font-weight: 560;
}

input,
select,
textarea {
  width: 100%;
  min-height: 48px;
  padding: 10px 13px;
  border: 1px solid rgba(23, 33, 29, 0.2);
  border-radius: 12px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.84);
  font-size: 16px;
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.8);
}

/* Focus used to be said twice and in the brand colour: an accent border on the
   field and an accent ring around it, which read as two outlines fighting.
   One quiet ring, from :focus-visible, is enough to say where the keyboard is. */

/* One shape for everything you press. The counts, the statuses and the tabs
   were already pills; a rectangle among them was the odd one out. */
.primary-button,
.secondary-button {
  display: inline-grid;
  place-items: center;
  padding: 11px 19px;
  border-radius: 999px;
  font-weight: 600;
  text-decoration: none;
}

.primary-button {
  border: 1px solid var(--ink);
  color: #fff;
  background: var(--ink);
}

.secondary-button {
  border: 1px solid rgba(23, 33, 29, 0.18);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.72);
}

.inline-button {
  width: auto;
}

.alert {
  margin: 16px 0;
  padding: 14px 16px;
  border-radius: 13px;
  text-align: left;
}

.alert-error {
  border: 1px solid #e1b5b0;
  color: #722820;
  background: #fff1ef;
}

.alert-success {
  border: 1px solid #b5d5c4;
  color: #204f3a;
  background: #eef8f2;
}

.page-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 22px;
}

.detail-heading {
  align-items: center;
  margin-top: 18px;
}

.count-badge {
  padding: 8px 13px;
  border-radius: 999px;
  color: #fff;
  background: var(--accent-dark);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
}

/* The one thing in the Inbox heading you can press. It takes the shape of
   the filter pill directly beneath it (see `.select-button`) rather than the
   solid slab `.count-badge` used to be: a number you act on belongs to the
   row of controls, not to the decoration. The only colour is the dot - a
   whole pill of terracotta at this size reads as mud, and says "alarm" for
   something that is simply work waiting.

   Pressed, it inverts to ink: that is also the state where the list below is
   filtered down to these requests, so the pill and the list say the same
   thing. */
.attention-pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 40px;
  padding: 0 17px;
  border: 1px solid rgba(23, 33, 29, 0.2);
  border-radius: 999px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.84);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.8);
  font-size: 0.95rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.attention-pill:hover {
  border-color: rgba(23, 33, 29, 0.36);
}

.attention-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent);
}

.attention-count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.attention-pill[aria-current="true"] {
  border-color: var(--ink);
  color: #fff;
  background: var(--ink);
  box-shadow: none;
}

.attention-pill[aria-current="true"] .attention-dot {
  background: rgba(255, 255, 255, 0.92);
}

.pull-refresh {
  --pull-distance: 0px;
  position: fixed;
  z-index: 8;
  top: calc(70px + env(safe-area-inset-top));
  left: 50%;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 38px;
  padding: 8px 13px;
  border: 1px solid rgba(23, 33, 29, 0.12);
  border-radius: 999px;
  opacity: 0;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.86);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  pointer-events: none;
  transform: translate(-50%, calc(-120% + var(--pull-distance)));
  transition: opacity 150ms ease, background-color 150ms ease;
}

.pull-refresh.is-active {
  opacity: 1;
}

.pull-refresh.is-ready {
  color: #fff;
  background: var(--green);
}

.pull-refresh-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
}

.pull-refresh.is-refreshing .pull-refresh-spinner {
  animation: pull-refresh-spin 700ms linear infinite;
}

@keyframes pull-refresh-spin {
  to {
    transform: rotate(360deg);
  }
}

.install-note {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 16px;
  padding: 13px 15px;
  border: 1px solid rgba(130, 71, 30, 0.2);
  border-radius: 13px;
  color: #554531;
  background: rgba(255, 249, 236, 0.66);
  font-size: 0.86rem;
}

.install-note[hidden] {
  display: none;
}

.install-note div {
  display: grid;
  gap: 2px;
}

.install-note button {
  flex: 0 0 44px;
  min-height: 44px;
  margin: -7px -9px -7px 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  color: #685a47;
  background: transparent;
  font-size: 1.45rem;
}

.filter-bar {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(150px, 0.8fr) auto;
  gap: 10px;
  margin-bottom: 16px;
}

/* With scripts there is no Apply, and what is left is two pills of one
   width. Neither is the more important half of the row. */
.filter-bar[data-live] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* The label is the field, so the glyph can be positioned inside it. A grid
   with a gap - which is what a label is everywhere else - would have put the
   magnifier above the pill. */
.filter-search {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0;
}

/* The backup's search glyph (see `.files-search-glyph`), on the smaller pill
   this row uses. */
.filter-search-glyph {
  position: absolute;
  left: 15px;
  width: 17px;
  height: 17px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.75;
  stroke-linecap: round;
  pointer-events: none;
}

.filter-search input {
  padding-left: 41px;
}

/* Safari's own clear button sits on the curve of a pill, and the field
   empties itself as soon as it is empty. */
.filter-search input::-webkit-search-decoration,
.filter-search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* The row is three things you press, side by side, so they take one shape -
   the button's. A 12px field between two pills was the odd one out. */
.filter-bar input,
.filter-bar select,
.status-form select,
.select-button {
  border-radius: 999px;
}

.filter-status {
  position: relative;
  display: block;
}

/* Covered, not removed: it still holds the value and still submits. */
.filter-status[data-select-ready] select {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* The chosen status is one word wide, not one word inside a fixed pill: a
   long one ("Waiting for files") is allowed to take the room it needs, and
   what has to give way is the search field beside it, never the label. */
.filter-bar[data-live] .select-button-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.select-button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: 48px;
  padding: 10px 18px;
  border: 1px solid rgba(23, 33, 29, 0.2);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.84);
  box-shadow: inset 0 1px rgba(255, 255, 255, 0.8);
  font: inherit;
  font-size: 16px;
  text-align: left;
}

.select-chevron {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  margin-top: -3px;
  border-right: 1.7px solid var(--muted);
  border-bottom: 1.7px solid var(--muted);
  transform: rotate(45deg);
}

.select-button[aria-expanded="true"] .select-chevron {
  margin-top: 3px;
  transform: rotate(225deg);
}

/* The global [hidden] rule is the same weight as a class and sits above this
   one, so `display: grid` below would quietly beat it and the menu would stand
   open for good. Said again here, where it wins. */
.select-menu[hidden] {
  display: none;
}

.select-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  right: 0;
  left: 0;
  display: grid;
  padding: 6px;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--panel-solid);
  box-shadow: 0 18px 44px rgba(23, 33, 29, 0.16);
}

.select-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  width: 100%;
  min-height: 42px;
  padding: 9px 12px;
  border: 0;
  border-radius: 13px;
  color: var(--ink);
  background: none;
  font: inherit;
  text-align: left;
}

/* No ring anywhere on this row - it was asked for twice. The menu still has to
   show where the keyboard is, so the row under it lights up instead. */
.filter-bar :focus-visible {
  outline: none;
}

.select-option:hover,
.select-option:focus {
  background: rgba(23, 33, 29, 0.05);
}

.select-option[data-selected] {
  background: rgba(23, 33, 29, 0.08);
  font-weight: 600;
}

.select-option-count {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.result-count {
  margin: 12px 2px;
  font-size: 0.88rem;
}

.result-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.refresh-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 6px 13px;
  border: 0;
  border-radius: 999px;
  color: var(--accent-dark);
  background: transparent;
  font-size: 0.88rem;
  font-weight: 600;
}

.refresh-button span {
  font-size: 1.15rem;
}

.request-list {
  display: grid;
  gap: 14px;
}

/* One person, one surface: the name and their requests inside a single figure,
   the requests as rows divided by hairlines. A name standing in the air above
   loose cards belonged to nothing the eye could see.

   The earlier version wrapped the cards in a tray and the two indents did not
   line up - but that was the tray's own padding adding to the card's, not the
   tray. Here the surface has none: the heading and every row carry the same
   18px, so one left edge runs the height of the block. */
.request-group {
  display: grid;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--panel-solid);
  box-shadow: 0 8px 24px rgba(23, 33, 29, 0.05);
}

/* Who they are on the left, how many they sent on the right. The count used
   to be a fragment inside the contact line and had to be read to be found;
   as a pill it is a number in a fixed place - and a block of one carries no
   pill at all, because the block already says it. */
.request-group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  padding: 13px 10px 12px 18px;
}

/* Rows of one surface now, not cards in their own right: the edges, the round
   corners and the shadow all belong to the block. The line above each row is
   what separates them, and the first one also parts the heading from the
   requests under it. */
.request-group .request-swipe {
  border-top: 1px solid var(--line);
  border-radius: 0;
  box-shadow: none;
}

.request-group .request-card {
  border: 0;
  border-radius: 0;
}

/* Kept after the rule that strips the borders, and only ever on the left:
   inside a shared surface the accent reads better than it did on a card,
   because it now marks one row among its neighbours. */
.request-group .request-unread {
  border-left: 5px solid var(--accent);
}

.request-group-who {
  margin: 0;
  min-width: 0;
  font-size: 0.96rem;
  font-weight: 600;
  line-height: 1.3;
}

/* Where they wrote from is context, not identity: it sits on the same line
   as the name without competing with it for the weight. */
.sender-country {
  color: var(--muted);
  font-weight: 400;
}

/* Set with the name, not under it: on a wide screen the heading is one line
   read as one thing, and a count two steps smaller read as a footnote to it. */
.request-group-count {
  flex: 0 0 auto;
  padding: 4px 11px;
  border-radius: 999px;
  background: rgba(23, 33, 29, 0.06);
  color: var(--muted);
  font-size: 0.96rem;
  font-weight: 560;
  white-space: nowrap;
}

/* Older than the freshest day in the block. Folded to one line, but the line
   carries the city and how it ended - a week later that is the whole reason to
   look at it. It is the last row of the same surface, parted by the same
   hairline, and opening it lets rows out below in the same figure. */
.request-older > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 3px 10px;
  padding: 11px 10px 11px 18px;
  border-top: 1px solid var(--line);
  font-size: 0.86rem;
  color: var(--muted);
  cursor: pointer;
  list-style: none;
}

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

.request-older > summary::after {
  margin-left: auto;
  content: "\2304";
}

.request-older[open] > summary::after {
  content: "\2303";
}

.request-older-lead {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--ink);
  font-weight: 600;
}

.request-older-list {
  display: grid;
}


.request-swipe {
  position: relative;
  display: flex;
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  border-radius: 18px;
  background: var(--panel-solid);
  box-shadow: 0 8px 24px rgba(23, 33, 29, 0.05);
  isolation: isolate;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  touch-action: pan-x pan-y pinch-zoom;
}

.request-swipe::-webkit-scrollbar {
  display: none;
}

.request-swipe:not([data-swipe-ready]) .swipe-rail,
.request-swipe:not([data-swipe-ready]) .swipe-delete-commit {
  display: none;
}

.swipe-rail,
.swipe-delete-commit {
  flex: 0 0 auto;
  display: flex;
  align-items: stretch;
}

.swipe-rail-start {
  flex-basis: 94px;
  background: var(--green);
  scroll-snap-align: start;
}

.swipe-rail-end {
  flex-basis: 184px;
  scroll-snap-align: end;
}

.swipe-delete-commit {
  flex-basis: calc(100% - 184px);
  align-items: center;
  justify-content: center;
  color: #fff;
  background: var(--red);
  font-weight: 600;
}

.swipe-action {
  display: grid;
  width: 92px;
  height: 100%;
  min-height: 44px;
  place-items: center;
  padding: 10px 8px;
  border: 0;
  border-radius: 0;
  color: #fff;
  background: transparent;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}

.swipe-action-read {
  width: 94px;
}

.swipe-action-reply {
  background: var(--blue);
}

.swipe-action-delete {
  background: var(--red);
}

.request-swipe[data-delete-armed] .swipe-rail-end {
  background: var(--red);
}

.request-swipe[data-delete-armed] .swipe-rail-end .swipe-action {
  visibility: hidden;
}

.request-card {
  position: relative;
  flex: 0 0 100%;
  padding: 18px 62px 18px 18px;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 18px;
  color: inherit;
  background: var(--panel-solid);
  scroll-snap-align: start;
}

/* A sheet laid over the card rather than a wrapper around it. The card still
   opens from anywhere you press, and the two things that lead elsewhere - the
   sender's website and the ••• button - sit above the sheet and keep their
   own click. */
.request-card-link {
  position: absolute;
  z-index: 1;
  inset: 0;
  border-radius: inherit;
}

.request-card-link:active {
  background: rgba(23, 33, 29, 0.04);
}

.card-more-button {
  position: absolute;
  z-index: 3;
  top: 8px;
  right: 8px;
  display: grid;
  width: 44px;
  height: 44px;
  place-items: center;
  border-radius: 999px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-decoration: none;
}

.card-more-button:active {
  background: rgba(23, 33, 29, 0.07);
}

.request-unread {
  border-left: 5px solid var(--accent);
}

/* Pills at the left, the date at the right, and nothing distributed in
   between. `space-between` was spreading whatever the row happened to hold,
   so a card that gained an unread dot gained a third item and parked its date
   in the middle: the date moved because the request was new, which is not
   something a date should do. */
.request-card-top {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* The date is the right edge of every card. The dot comes before it in the
   markup, so this margin lands on whichever of the two starts the right-hand
   group, and the date column stays true down the list either way. */
.request-card-top > time,
.request-card-top > .new-dot {
  margin-left: auto;
}

.request-card-top > .new-dot ~ time {
  margin-left: 0;
}

.request-card-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 13px;
  font-size: 1.04rem;
  font-weight: 600;
}

/* Only ever shown in place of the words, on a phone. */
.delivery-icon {
  display: none;
  align-items: center;
  gap: 3px;
}

.delivery-icon svg {
  width: 12px;
  height: 12px;
}

.delivery-count {
  font-variant-numeric: tabular-nums;
}

.status-pill {
  display: inline-flex;
  align-items: center;
  min-height: 30px;
  padding: 5px 10px;
  border-radius: 999px;
  font-size: 0.76rem;
  font-weight: 600;
  /* A pill is one word to the eye even when it is three: beside a long name
     on a phone, "Owed a reply" broke into a column of three. The row wraps
     around the pill; the pill never wraps inside itself. */
  white-space: nowrap;
}

.status-new {
  color: #71370e;
  background: #f7dfc6;
}

.status-in_progress {
  color: #244b68;
  background: #dcebf5;
}

/* Read but not yet acted on. Quieter than New - it no longer asks to be
   noticed - and still plainly unfinished, so it does not borrow the calm of
   Replied. */
.status-read {
  color: #5c5f5d;
  background: #eceeec;
}

.status-replied {
  color: #24513b;
  background: #dceee4;
}

.status-closed {
  color: #505753;
  background: #e7e9e7;
}

.status-spam {
  color: #7a2922;
  background: #f4d9d6;
}

.new-dot {
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}

.request-meta,
.request-location,
.request-excerpt {
  margin: 7px 0 0;
}

.request-location {
  font-weight: 560;
}

.request-excerpt {
  display: -webkit-box;
  overflow: hidden;
  color: #444d48;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 22px;
}

.detail-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin: 0;
}

.detail-grid div {
  min-width: 0;
}

/* Delivered and To build are one sequence, not two of a kind, so they break
   the two-column rhythm and take a line each. */
.detail-grid-span {
  grid-column: 1 / -1;
}

/* One pill per format, at the page's own text size and weight. A pill is a
   shape around a word, not an emphasis on it: bold inside one made the row
   shout over the labels beside it, and the colour was already doing the
   work. Tight, because there can be four of them on one line. */
.format-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 2px;
}

.format-pills .status-pill {
  min-height: 0;
  padding: 3px 9px;
  /* A shade under the text around it, and the shade is the point: white on a
     filled shape reads larger and heavier than the same size on paper, so
     matching the number would leave the pills looking bigger than the labels
     they sit among. This is the correction that makes them look equal. */
  font-size: 0.94em;
  font-weight: inherit;
}

/* The pill carries its own colour, and an underline under a filled shape
   reads as a mistake. Everything else about it is the pill the rest of the
   hub already uses. */
.detail-grid a.status-pill {
  text-decoration: none;
}

/* What follows the value without competing with it: how far the download
   got, or where the rest of the archives are. Smaller and set back, so the
   formats stay the thing being read. */
.detail-aside {
  font-size: 0.86rem;
}

/* One block per archive, in a 300px column: four short lines each, tight
   enough that four deliveries read as a list rather than as a page. */
.parcel-list {
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.parcel-list > li {
  padding: 11px 0 0;
  margin: 11px 0 0;
  border-top: 1px solid var(--line);
}

.parcel-list > li:first-child {
  padding-top: 0;
  margin-top: 0;
  border-top: 0;
}

/* How it was sent and when. Both are qualifiers, so both take the quiet
   size; the line that says what happened is the one at full size below. */
.parcel-line {
  margin: 0;
  color: var(--muted);
  font-size: 0.84rem;
}

.parcel-line time {
  color: inherit;
}

/* What it weighed and how it went - the one line of the block that is read.
   The state carries colour only when it is bad news; "not opened" three
   times over is not news at all. */
.parcel-facts {
  margin: 1px 0 0;
}

.parcel-facts .parcel-none {
  color: var(--muted);
}

/* What was in it. Smallest and quietest on the block: the operator picked
   these files themselves, so the names confirm rather than inform. */
.parcel-names {
  margin: 1px 0 0;
  color: var(--muted);
  font-size: 0.84rem;
  line-height: 1.45;
  overflow-wrap: anywhere;
}

/* The way to raise an invoice, under whatever the panel had to say first -
   the records, or the sentence that stands in for them. The same 22px this
   file uses to part a block from the one above it, and no width: the button
   is as wide as its own words. */
.money-action {
  margin-top: 22px;
}

a.detail-aside {
  color: var(--muted);
}

.detail-grid dt {
  margin-bottom: 4px;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
}

.detail-grid dd {
  margin: 0;
  /* No pre-wrap here. It preserved the template's own indentation and pushed
     values away from their labels twice; the only field that needs line breaks
     kept - the project note - sets it on itself. */
}

.break-text {
  overflow-wrap: anywhere;
}

.note-block {
  margin-top: 22px;
  padding-top: 19px;
  border-top: 1px solid var(--line);
}

.note-block p {
  margin-bottom: 0;
  white-space: pre-wrap;
}

.status-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
}

/* Contacts sit above the field grid, not inside it: that grid puts its labels
   above their values, and a left-labelled row placed in it left a void half
   the panel wide. The label column is exactly as wide as the longest label,
   so the pair reads as one thing. */
.contact-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: baseline;
  gap: 7px 16px;
  margin: 0 0 22px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--line);
}

.contact-list dt {
  margin: 0;
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.contact-list dd {
  margin: 0;
  overflow-wrap: anywhere;
}

.contact-line {
  font-size: inherit;
}

.contact-free {
  color: var(--muted);
}

/* The other requests from the same address. One line each: where, what, when -
   enough to know whether this one has already been answered. */
.also-sent {
  margin: 0;
  padding: 0;
  list-style: none;
}

.also-sent li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  padding: 3px 0;
}

/* Grey already tells the city apart from what follows it. A second, smaller
   size on top of that was one distinction too many: it made a single line of
   the card read as three different kinds of thing. */
.also-sent time,
.also-formats {
  color: var(--muted);
}

.read-state-form {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--line);
  font-weight: 600;
}

#actions,
#reply {
  scroll-margin-top: calc(82px + env(safe-area-inset-top));
}

.action-sheet {
  width: min(520px, calc(100% - 24px));
  max-height: calc(100vh - env(safe-area-inset-top) - 16px);
  margin: auto auto 0;
  padding: 14px 18px max(18px, env(safe-area-inset-bottom));
  overflow: auto;
  border: 0;
  border-radius: 24px 24px 0 0;
  color: var(--ink);
  background: rgba(245, 244, 241, 0.94);
  box-shadow: 0 -18px 50px rgba(23, 33, 29, 0.22);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.action-sheet::backdrop {
  background: rgba(10, 16, 13, 0.46);
  backdrop-filter: blur(3px);
}

.action-sheet-handle {
  width: 44px;
  height: 5px;
  margin: 0 auto 15px;
  border-radius: 999px;
  background: #b6bab4;
}

.action-sheet h2 {
  margin: 0;
  font-size: 1.25rem;
  font-weight: 600;
}

.action-sheet > p {
  margin: 5px 0 16px;
}

.action-sheet-actions {
  display: grid;
  gap: 9px;
}

.action-sheet-actions button,
.action-sheet-actions a {
  width: 100%;
  min-height: 48px;
  padding: 11px 15px;
  border: 1px solid rgba(23, 33, 29, 0.18);
  border-radius: 13px;
  color: var(--ink);
  background: rgba(255, 255, 255, 0.78);
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}

.action-sheet-actions button:disabled {
  opacity: 0.45;
}

.action-sheet-actions .danger-action {
  color: var(--red);
}

.action-sheet-actions .desktop-sheet-reply {
  display: none;
}

.integration-state {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 16px;
}

.integration-state p {
  margin: 5px 0 0;
  color: var(--muted);
}

.integration-dot {
  flex: 0 0 auto;
  width: 12px;
  height: 12px;
  margin-top: 5px;
  border-radius: 50%;
  background: #c88735;
}

.timeline {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.timeline li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 4px 12px;
  padding: 13px 0;
  border-top: 1px solid var(--line);
}

.timeline time {
  grid-column: 1 / -1;
  font-size: 0.82rem;
}

/* The sentence the whole page is there to say. Set at reading size rather than
   as a heading: it is prose, and it is meant to be read, not glanced at. */
.stats-headline {
  margin: 0 0 8px;
  font-size: 1.06rem;
  font-weight: 560;
}

/* A named state and its count. No bars: at this size a bar chart of five
   states is decoration, and a bar of one is a lie about precision. */
.stats-states {
  margin: 0;
  padding: 0;
  list-style: none;
}

.stats-states li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 6px 18px;
  padding: 9px 0 9px 11px;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
}

.stats-states li:first-child {
  border-top: 0;
}

.stats-states strong,
.formats-bar-figure strong {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* The alert stripes, restated at the list's own specificity: the base rule
   above pins border-left with a shorthand, and a bare .row-alarm class would
   lose to it and never paint. */
.stats-states li.row-alarm {
  border-left-color: var(--red);
}

.stats-states li.row-warn {
  border-left-color: rgba(184, 102, 47, 0.55);
}

/* The limits of the page, in plain sentences. Deliberately grey and unnumbered
   so nothing here is mistaken for a measurement. */
.stats-blind {
  margin: 0;
  padding: 0 0 0 18px;
  color: var(--muted);
}

.stats-blind li {
  margin: 6px 0;
}

/* A blind-spot line has no border to stripe, so an alarming one - "these
   zeros mean no counter" - speaks through its ink instead. */
.stats-blind li.row-alarm {
  color: var(--red);
}

/* The bulletin's window switcher: the same pill everything pressable wears.
   The chosen window is inked in, not underlined - a pressed key, not a link. */
.bulletin-switch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 7px;
  margin: 0 0 18px;
}

.bulletin-tab {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 5px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  color: var(--ink);
  font-size: 0.82rem;
  text-decoration: none;
  transition: background 0.16s ease, border-color 0.16s ease;
}

.bulletin-tab:hover {
  border-color: rgba(23, 33, 29, 0.24);
  background: rgba(255, 255, 255, 0.86);
}

.bulletin-tab[aria-current="page"] {
  border-color: var(--ink);
  background: var(--ink);
  color: var(--paper);
}

.bulletin-step {
  display: inline-flex;
  gap: 7px;
  margin-left: auto;
}

/* Seven numbers a glance can hold. Values wear the Insights weight - light,
   never bold - and proportional figures, because nothing lines up under them. */
.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin: 0 0 14px;
  padding: 0;
  list-style: none;
}

.stat-tile {
  display: grid;
  gap: 3px;
  align-content: start;
  padding: 12px 14px 13px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.5);
}

.stat-tile-label,
.formats-bar-rank {
  color: var(--muted);
  font-size: 0.71rem;
  font-weight: 430;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.stat-tile-value,
.formats-verdict {
  font-size: 1.32rem;
  font-weight: 440;
  letter-spacing: -0.01em;
}

/* The movement against the previous window. Green got better, red got worse;
   the sign is spelled out, so colour is never the only carrier. */
.stat-delta {
  color: var(--muted);
  font-size: 0.74rem;
  font-weight: 540;
  font-variant-numeric: tabular-nums;
}

.stat-delta-up {
  color: var(--green);
}

.stat-delta-down {
  color: var(--red);
}

/* What a figure used to be, said in words rather than a second column. */
.was,
.formats-of,
.formats-bar-share,
.formats-column-count {
  color: var(--muted);
  font-size: 0.78rem;
  font-style: normal;
  font-weight: 400;
}

.new-mark {
  margin-left: 4px;
  color: var(--green);
  font-size: 0.72rem;
  font-style: normal;
  font-weight: 540;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.seo-movers {
  overflow-wrap: anywhere;
}

.stats-ranking {
  display: grid;
  gap: 16px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.stats-ranking li,
.stats-ranking li > div {
  display: grid;
  gap: 7px;
}

.stats-ranking li > div {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
}

.stats-ranking progress {
  width: 100%;
  height: 8px;
  overflow: hidden;
  border: 0;
  border-radius: 999px;
  appearance: none;
  background: #e0ddd4;
}

.stats-ranking progress::-webkit-progress-bar {
  border-radius: 999px;
  background: #e0ddd4;
}

.stats-ranking progress::-webkit-progress-value {
  border-radius: 999px;
  background: var(--accent);
}

.stats-ranking progress::-moz-progress-bar {
  border-radius: 999px;
  background: var(--accent);
}

/* What people ask for. Two columns - free samples and paid asks - because
   summed into one bar the samples drown the quotes and the chart points the
   wrong way. Each bar runs against its own column's leader; the hatched end
   is what the shelf did not hold. Lengths are SVG attributes, not inline
   styles: the hub's CSP allows no style attribute. */
.formats-verdict {
  display: block;
  margin: -6px 0 16px;
}

.formats-verdict strong {
  font-weight: 600;
}

.formats-defs {
  position: absolute;
  width: 0;
  height: 0;
}

.formats-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  margin: 4px 0 12px;
}

.formats-column {
  padding: 16px 16px 12px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: #fbfaf8;
}

.formats-column-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 10px;
  margin: 0 0 14px;
}

.formats-column-head h3 {
  margin: 0;
}

.formats-column-count {
  margin-left: auto;
}

.formats-bars {
  display: grid;
  gap: 13px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.formats-bar {
  display: grid;
  grid-template-columns: 1.4em minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 5px 8px;
  cursor: default;
}

.formats-bar-rank {
  font-variant-numeric: tabular-nums;
}

.formats-bar-figure {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
}

.formats-bar-track {
  grid-column: 2 / -1;
  display: block;
  width: 100%;
  height: 10px;
  overflow: visible;
}

.formats-bar-rail {
  fill: rgba(23, 33, 29, 0.06);
}

.formats-bar-fill {
  fill: var(--series);
  transition: opacity 0.16s ease;
}

/* The 2px surface ring keeps the hatched end its own segment, not a smudge
   on the solid one. */
.formats-bar-built {
  fill: url(#formats-hatch);
  stroke: #fbfaf8;
  stroke-width: 2;
}

.formats-bar:hover .formats-bar-fill {
  opacity: 0.82;
}

.formats-other {
  margin: 12px 0 0;
}

.formats-legend-hatch {
  display: inline-block;
  width: 18px;
  height: 8px;
  margin: 0 2px 0 4px;
  border-radius: 999px;
  background: repeating-linear-gradient(135deg, #2c8f63 0 2px, #cfe6da 2px 4.5px);
  vertical-align: middle;
}

.formats-words {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 0 0 10px;
  padding: 0;
  list-style: none;
}

.formats-word {
  min-height: 32px;
}

@media (max-width: 760px) {
  .formats-columns {
    grid-template-columns: minmax(0, 1fr);
  }
}


.stats-chart {
  display: block;
  width: 100%;
  height: 104px;
  overflow: visible;
}

.stats-chart line {
  stroke: var(--line);
  stroke-width: 1;
}

.stats-chart rect {
  fill: var(--accent);
}

.stats-chart rect.stats-chart-zero {
  fill: #d8d6ce;
}

.stats-chart-labels {
  display: flex;
  justify-content: space-between;
  margin-top: 7px;
  color: var(--muted);
  font-size: 0.75rem;
}

/* ── the thirty-day chart ─────────────────────────────────────────────────
   Four lines, one point per day. The four hues are fixed to the four things
   counted and never reassigned - a line keeps its colour whatever else is on
   the chart - and they were checked for separation under colour blindness
   rather than picked by eye. Identity never rests on colour alone either:
   every line is named in the key, in the crosshair, and in the table.

   The lightness of each hue is chosen against the panel, not against white:
   this is the surface a line is actually read on, and it is what the dots and
   the crosshair wear as their ring. */
.graph {
  --graph-surface: #fbfaf8;
  margin: 0 0 20px;
}

.graph [data-series="free"],
.graph [data-series="visits"],
.graph [data-series="bing"] {
  --series: #316ea8;
}

.graph [data-series="owned"],
.graph [data-series="views"],
.graph [data-series="google"] {
  --series: #c8621f;
}

.graph [data-series="taken"],
.graph [data-series="sample"],
.formats [data-series="sample"] {
  --series: #2c8f63;
}

.graph [data-series="by_hand"],
.graph [data-series="ai"],
.graph [data-series="quote"],
.formats [data-series="quote"] {
  --series: #8a4b96;
}

.graph-keys {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  margin: 0 0 16px;
  padding: 0;
  list-style: none;
}

/* The key is also the control: pressing one puts a single line in front and
   holds the other three back. On a phone that is the only way to pull one
   line out of four, and there is nothing to hover with. */
.graph-key,
.formats-word {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 36px;
  padding: 6px 12px 6px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  color: var(--ink);
  font-size: 0.82rem;
  transition: background 0.16s ease, border-color 0.16s ease, opacity 0.16s ease;
}

.graph-key:hover:not([disabled]) {
  border-color: rgba(23, 33, 29, 0.24);
  background: rgba(255, 255, 255, 0.86);
}

.graph-key[disabled] {
  opacity: 0.55;
  cursor: default;
}

.graph-key-mark {
  width: 15px;
  height: 3px;
  border-radius: 999px;
  background: var(--series);
}

.graph-key-total,
.formats-word strong {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.graph-plot {
  position: relative;
  border-radius: 12px;
}

.graph-plot:focus-visible {
  outline: 2px solid rgba(23, 33, 29, 0.3);
  outline-offset: 4px;
}

.graph-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
  font-size: 11px;
  touch-action: pan-y;
}

.graph-weekends rect {
  fill: rgba(23, 33, 29, 0.028);
}

/* The bucket the clock is still inside. An hour of a month plotted next to
   whole months is not a fall, and the shading says so before the caption
   has to. */
.graph-filling rect {
  fill: rgba(184, 102, 47, 0.07);
}

.graph-grid line {
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Zero is the floor, not another gridline: a line resting on it is at rest,
   and that has to read differently from a line passing a value. */
.graph-grid line:first-child {
  stroke: rgba(23, 33, 29, 0.24);
}

.graph-scale text,
.graph-days text {
  fill: var(--muted);
  font-size: inherit;
  font-variant-numeric: tabular-nums;
}

.graph-line {
  fill: none;
  stroke: var(--series);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.graph-area {
  fill: var(--series);
  opacity: 0;
  transition: opacity 0.18s ease;
}

.graph-series {
  transition: opacity 0.18s ease;
}

/* One line in front, the rest held back - never hidden. A line taken off the
   chart would change what the chart says; a line stepped back does not. */
.graph[data-graph-focus] .graph-series,
.graph[data-graph-focus] .graph-key:not([disabled]) {
  opacity: 0.16;
}

.graph[data-graph-focus="free"] .graph-series[data-series="free"],
.graph[data-graph-focus="owned"] .graph-series[data-series="owned"],
.graph[data-graph-focus="taken"] .graph-series[data-series="taken"],
.graph[data-graph-focus="by_hand"] .graph-series[data-series="by_hand"],
.graph[data-graph-focus="visits"] .graph-series[data-series="visits"],
.graph[data-graph-focus="views"] .graph-series[data-series="views"],
.graph[data-graph-focus="bing"] .graph-series[data-series="bing"],
.graph[data-graph-focus="google"] .graph-series[data-series="google"],
.graph[data-graph-focus="ai"] .graph-series[data-series="ai"],
.graph[data-graph-focus="sample"] .graph-series[data-series="sample"],
.graph[data-graph-focus="quote"] .graph-series[data-series="quote"],
.graph[data-graph-focus="free"] .graph-key[data-series="free"],
.graph[data-graph-focus="owned"] .graph-key[data-series="owned"],
.graph[data-graph-focus="taken"] .graph-key[data-series="taken"],
.graph[data-graph-focus="by_hand"] .graph-key[data-series="by_hand"],
.graph[data-graph-focus="visits"] .graph-key[data-series="visits"],
.graph[data-graph-focus="views"] .graph-key[data-series="views"],
.graph[data-graph-focus="bing"] .graph-key[data-series="bing"],
.graph[data-graph-focus="google"] .graph-key[data-series="google"],
.graph[data-graph-focus="ai"] .graph-key[data-series="ai"],
.graph[data-graph-focus="sample"] .graph-key[data-series="sample"],
.graph[data-graph-focus="quote"] .graph-key[data-series="quote"] {
  opacity: 1;
}

.graph[data-graph-focus="free"] .graph-series[data-series="free"] .graph-area,
.graph[data-graph-focus="owned"] .graph-series[data-series="owned"] .graph-area,
.graph[data-graph-focus="taken"] .graph-series[data-series="taken"] .graph-area,
.graph[data-graph-focus="by_hand"] .graph-series[data-series="by_hand"] .graph-area,
.graph[data-graph-focus="visits"] .graph-series[data-series="visits"] .graph-area,
.graph[data-graph-focus="views"] .graph-series[data-series="views"] .graph-area,
.graph[data-graph-focus="bing"] .graph-series[data-series="bing"] .graph-area,
.graph[data-graph-focus="google"] .graph-series[data-series="google"] .graph-area,
.graph[data-graph-focus="ai"] .graph-series[data-series="ai"] .graph-area,
.graph[data-graph-focus="sample"] .graph-series[data-series="sample"] .graph-area,
.graph[data-graph-focus="quote"] .graph-series[data-series="quote"] .graph-area {
  opacity: 0.1;
}

.graph-key[aria-pressed="true"] {
  border-color: rgba(23, 33, 29, 0.28);
  background: rgba(255, 255, 255, 0.92);
}

.graph-cursor line {
  stroke: rgba(23, 33, 29, 0.26);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.graph-cursor circle {
  fill: var(--series);
  stroke: var(--graph-surface);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* The readout follows the crosshair and lists every line at that day, so the
   pointer never has to land on a 2px stroke to get a number. */
.graph-readout {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  min-width: 168px;
  padding: 10px 12px 11px;
  border: 1px solid var(--line);
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.92);
  box-shadow: 0 12px 30px rgba(23, 33, 29, 0.13);
  font-size: 0.8rem;
  pointer-events: none;
  transform: translate(var(--readout-x, 0), var(--readout-y, 0));
  -webkit-backdrop-filter: saturate(150%) blur(14px);
  backdrop-filter: saturate(150%) blur(14px);
}

.graph-readout-day {
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.graph-readout-row {
  display: grid;
  grid-template-columns: 15px auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 2px 0;
}

.graph-readout-row span:first-child {
  height: 3px;
  border-radius: 999px;
  background: var(--series);
}

.graph-readout-row strong {
  font-variant-numeric: tabular-nums;
  font-weight: 640;
}

.graph-readout-row em {
  color: var(--muted);
  font-style: normal;
}

.graph figcaption {
  margin-top: 13px;
  color: var(--muted);
  font-size: 0.83rem;
  line-height: 1.55;
}

.graph figcaption strong {
  color: var(--ink);
  font-weight: 600;
}

/* The same numbers without colour, pointer or script. Closed by default: it
   is the proof the chart is honest, not the way the chart is read. */
.graph-numbers {
  margin-top: 14px;
  font-size: 0.85rem;
}

.graph-numbers summary {
  color: var(--muted);
  cursor: pointer;
}

.graph-numbers table {
  width: 100%;
  margin-top: 10px;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

.graph-numbers th,
.graph-numbers td {
  padding: 6px 10px 6px 0;
  border-bottom: 1px solid var(--line);
  text-align: right;
  white-space: nowrap;
}

.graph-numbers thead th,
.graph-numbers tbody th {
  color: var(--muted);
  font-weight: 500;
  text-align: left;
}

/* The chart keeps its proportions as it narrows, so its type would shrink
   with it. The type is set back up instead - it is read at the same size on a
   phone as on a desk. */
@media (max-width: 760px) {
  .graph-svg {
    font-size: 15px;
  }
}

@media (max-width: 480px) {
  .graph-svg {
    font-size: 20px;
  }

  .graph-key,
  .formats-word {
    font-size: 0.78rem;
  }
}

/* Each line arrives whole, one after another. It used to draw itself left to
   right, and for the second that took, a line stopped halfway across the month
   said the days after it were empty. An animation that can misstate the data
   for even a frame is not worth the flourish; a line is either there or it is
   not yet. Motion the reader did not ask for is still motion nobody gets. */
@media (prefers-reduced-motion: no-preference) {
  .graph-series {
    animation: graph-arrive 0.42s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  }

  .graph-series[data-series="owned"],
  .graph-series[data-series="views"],
  .graph-series[data-series="google"],
  .graph-series[data-series="quote"] {
    animation-delay: 0.06s;
  }

  .graph-series[data-series="taken"],
  .graph-series[data-series="ai"] {
    animation-delay: 0.12s;
  }

  .graph-series[data-series="by_hand"] {
    animation-delay: 0.18s;
  }
}

@keyframes graph-arrive {
  from {
    opacity: 0;
    transform: translateY(7px);
  }
}

.timeline small {
  grid-column: 1 / -1;
  overflow-wrap: anywhere;
}

/* The client's history is the same row in a wide panel, and its detail is a
   whole list of formats rather than the few words Activity carries. In two
   columns that detail took the width, the title column collapsed to nothing
   and the place printed on top of it. Here the detail gets a line, the way a
   delivery in Files does. */
.client-history li span {
  grid-column: 1 / -1;
}

.empty-state {
  padding: 70px 24px;
  border: 1px dashed rgba(23, 33, 29, 0.22);
  border-radius: 16px;
  text-align: center;
}

.files-empty {
  display: grid;
  min-height: 340px;
  place-items: center;
  align-content: center;
  gap: 8px;
  text-align: center;
}

.files-empty svg {
  width: 50px;
  height: 50px;
  margin-bottom: 8px;
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.35;
}

.files-empty h2,
.files-empty p {
  margin: 0;
}

.files-empty p {
  max-width: 420px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

@media (max-width: 620px) {
  .app-main {
    padding-top: max(24px, env(safe-area-inset-top));
  }

  .filter-bar,
  .status-form {
    grid-template-columns: 1fr;
  }

  /* One line, still: a stack of three was what the bar looked like before
     the Apply button went, and a stack of two is no better - the whole row
     is two taps and they fit side by side, half each. */
  .filter-bar[data-live] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  /* Tighter inside the pill: at half of 360px the difference between 18 and
     14 is a whole word of the status. */
  .filter-bar[data-live] .select-button {
    padding-right: 14px;
    padding-left: 14px;
  }

  /* The menu is as wide as the words in it, not as the button it hangs off,
     which at this width would cut "Waiting for files" in half. */
  .filter-bar[data-live] .select-menu {
    left: auto;
    min-width: max(100%, 62vw);
  }

  .detail-grid {
    grid-template-columns: 1fr;
  }

  /* The ••• only ever opened the sheet the swipe rails already carry, and it
     was holding 56px of every card's width hostage to do it. Gone on touch;
     on a pointer, where there is nothing to swipe with, it stays. */
  .card-more-button {
    display: none;
  }

  .request-card {
    padding-right: 18px;
  }

  /* On a phone the top line has to hold pills, a date and a dot across 360px,
     so the date drops to the pills' size - it is the least urgent thing there.
     On a wide screen there is room, and it stays at reading size. */
  .request-card-top time {
    font-size: 0.76rem;
  }

  .request-group-count {
    padding: 3px 9px;
    font-size: 0.74rem;
  }

  /* One glyph instead of three labels of three different lengths, all pills
     the same width so the row starts at the same place on every card. */
  .delivery-text {
    display: none;
  }

  .delivery-icon {
    display: inline-flex;
  }

  .delivery-broken,
  .delivery-done,
  .delivery-owed,
  .delivery-waiting {
    min-width: 40px;
    padding-right: 8px;
    padding-left: 8px;
    justify-content: center;
  }

  .page-heading {
    align-items: flex-start;
  }

  .page-heading h1 {
    font-size: 2rem;
  }

  .detail-heading {
    align-items: center;
  }
}

@media (min-width: 621px) {
  .action-sheet {
    margin: auto;
    border-radius: 24px;
  }
}

/* Statistics sections. Exactly one of the two navigations is ever on screen:
   tabs above the content on a phone, sub-items in the sidebar on a wide
   screen. */
.app-subnav {
  display: none;
}

.stats-tabs,
.mail-tabs {
  display: flex;
  gap: 6px;
  margin: 0 0 18px;
  padding-bottom: 2px;
  overflow-x: auto;
  scrollbar-width: none;
}

.stats-tabs::-webkit-scrollbar,
.mail-tabs::-webkit-scrollbar {
  display: none;
}

.stats-tabs a,
.mail-tabs a {
  flex: 0 0 auto;
  padding: 9px 15px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel);
  color: var(--muted);
  font-size: 0.9rem;
  font-weight: 520;
  text-decoration: none;
  white-space: nowrap;
}

.stats-tabs a[aria-current="page"],
.mail-tabs a[aria-current="page"] {
  border-color: transparent;
  background: var(--ink);
  color: var(--paper);
}

@media (min-width: 1000px) {
  .app-sidebar {
    position: fixed;
    z-index: 6;
    inset: 0 auto 0 0;
    display: flex;
    flex-direction: column;
    width: var(--app-sidebar-width);
    padding: 26px 16px 22px;
    overflow-y: auto;
    color: #fff;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
    background:
      radial-gradient(circle at 20% 0, rgba(184, 102, 47, 0.16), transparent 18rem),
      linear-gradient(180deg, #18231e, #121a16);
    box-shadow: 18px 0 44px rgba(10, 16, 13, 0.12);
  }

  .app-sidebar-brand {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 44px;
    margin: 0 4px 30px;
  }

  .app-sidebar-brand .brand-mark {
    flex: 0 0 auto;
    color: var(--ink);
    background: #f0eadf;
    box-shadow: 0 8px 20px rgba(8, 13, 11, 0.18);
  }

  .app-sidebar-brand > span:last-child {
    display: grid;
    gap: 1px;
  }

  .app-sidebar-brand strong {
    font-size: 0.92rem;
    font-weight: 600;
    letter-spacing: -0.02em;
  }

  .app-sidebar-brand small {
    color: rgba(255, 255, 255, 0.48);
    font-size: 0.69rem;
  }

  .app-navigation-desktop .app-nav-item {
    gap: 10px;
    padding: 4px 8px;
    color: rgba(255, 255, 255, 0.7);
    font-weight: 500;
  }

  .app-navigation-desktop .app-nav-glyph {
    width: 36px;
    height: 36px;
    flex: 0 0 auto;
    border-radius: 11px;
    color: inherit;
  }

  .app-navigation-desktop .app-nav-glyph svg {
    width: 20px;
    height: 20px;
  }

  .app-navigation-desktop .app-nav-item:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.055);
  }

  .app-navigation-desktop .app-nav-item[aria-current="page"] {
    color: #fff;
    background: rgba(255, 255, 255, 0.1);
  }

  .app-navigation-desktop .app-nav-item[aria-current="page"] .app-nav-glyph {
    color: #f2e6d9;
    background: rgba(184, 102, 47, 0.18);
  }

  .stats-tabs {
    display: none;
  }

  .app-subnav {
    display: grid;
    gap: 1px;
    margin: 2px 0 6px 21px;
    padding-left: 13px;
    border-left: 1px solid rgba(255, 255, 255, 0.14);
  }

  .app-subnav a {
    padding: 7px 10px;
    border-radius: 10px;
    color: rgba(255, 255, 255, 0.62);
    font-size: 0.84rem;
    text-decoration: none;
  }

  .app-subnav a:hover {
    color: #fff;
    background: rgba(255, 255, 255, 0.055);
  }

  .app-subnav a[aria-current="page"] {
    color: #fff;
    background: rgba(255, 255, 255, 0.1);
  }

  .app-nav-label {
    font-size: 0.88rem;
  }

  .app-sidebar > .menu-signout {
    margin-top: auto;
    padding: 24px 8px 0;
  }

  .sidebar-signout {
    min-height: 42px;
    padding: 0;
    border: 0;
    color: rgba(255, 255, 255, 0.55);
    background: transparent;
    font-size: 0.82rem;
  }

  .sidebar-signout:hover {
    color: #fff;
  }

  .mobile-session-panel {
    display: none;
  }

  .has-app-navigation .app-main {
    width: min(820px, calc(100% - var(--app-sidebar-width)));
    margin-right: 0;
    margin-left: var(--app-sidebar-width);
    padding: 42px;
  }

  .has-app-navigation .app-main-inbox,
  .has-app-navigation .app-main-detail {
    width: min(1440px, calc(100% - var(--app-sidebar-width)));
  }

  .has-app-navigation .app-main-statistics {
    width: min(1120px, calc(100% - var(--app-sidebar-width)));
  }

  .has-app-navigation .app-main-files,
  .has-app-navigation .app-main-insights {
    width: min(1120px, calc(100% - var(--app-sidebar-width)));
  }

  /* Wide enough for the workbench: the letter at its native 600 beside the
     forms that repaint it. */
  .has-app-navigation .app-main-letters {
    width: min(1440px, calc(100% - var(--app-sidebar-width)));
  }

  .app-main-inbox .request-swipe {
    overflow-x: hidden;
    scroll-snap-type: none;
    touch-action: auto;
  }

  .app-main-inbox .swipe-rail,
  .app-main-inbox .swipe-delete-commit {
    display: none;
  }

  .action-sheet-actions .desktop-sheet-reply {
    display: grid;
    place-items: center;
  }
}

@media (min-width: 1200px) {
  /* Two stacks side by side, each packing its own panels. Placing panels
     directly in a two-column grid tied them together by row: Status shared a
     row with Request details and inherited its height, leaving a void the size
     of the difference. Nothing in the right column has any business waiting
     for the left one. */
  .detail-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    column-gap: 24px;
    align-items: start;
  }

  .detail-col {
    display: grid;
    align-content: start;
    gap: 24px;
    min-width: 0;
  }

  .detail-col > .panel {
    margin: 0;
  }
}

@media (min-width: 1320px) {
  /* Two stacks side by side, each packing its own cards tight - a grid would
     tie the two cards of a row to whichever is taller. The stacks are built by
     the script that deals the cards out alternately; until it has, and on any
     screen where it does not, the list above stays a single column. */
  .app-main-inbox .request-list[data-columns] {
    display: flex;
    align-items: flex-start;
    gap: 11px;
  }

  .app-main-inbox .request-column {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    /* Down a column the gap separates people, so it matches the one the
       single-column list uses; across the two it only separates columns. */
    gap: 14px;
    min-width: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto;
    transition-duration: 0.01ms;
    animation-duration: 0.01ms;
  }
}

/* Conversation thread on the inquiry detail page.
   The message text is the content; delivery telemetry stays folded away
   and only surfaces when something actually went wrong. */
/* Each letter in its own card. A rule between them said "next item"; a card
   says "this is a thing that was sent", which is what the operator is
   looking at - the message as it landed in somebody's mail, signature and
   all, not a row in a log. */
.thread {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.thread-item {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--panel-solid);
}

.thread-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 10px;
  margin-bottom: 6px;
}

/* Who wrote it needs to be legible, not loud: the card already separates
   one letter from the next, and bold on a two-word label made every message
   shout its own header. */
.thread-author {
  color: var(--muted);
}

.thread-time {
  color: var(--muted);
  font-size: 0.82rem;
}

.thread-state {
  margin-left: auto;
  font-size: 0.78rem;
  color: var(--muted);
  white-space: nowrap;
}

.thread-state-ok::before {
  content: "\2713\00a0";
  color: var(--green);
}

.thread-state-bad {
  color: var(--red);
  font-weight: 600;
}

.thread-state-wait {
  color: var(--blue);
}

.thread-subject {
  margin: 0 0 6px;
  color: var(--muted);
  font-size: 0.86rem;
}

.thread-body {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.thread-item-auto .thread-body {
  color: var(--muted);
}

/* The letter, in the card, at the width it sets to - not the window's. The
   height is measured off the letter once it loads; the value here only has
   to be sane for the moment before that. */
.thread-letter {
  margin: 6px 0 0;
}

.thread-letter iframe {
  display: block;
  width: 600px;
  max-width: 100%;
  height: 320px;
  border: 0;
  background: #fff;
}

.thread-body-fold summary,
.thread-tech summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.86rem;
}

.thread-body-fold[open] summary,
.thread-tech[open] summary {
  margin-bottom: 8px;
}

.thread-tech {
  margin-top: 10px;
}

.thread-tech dl {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 3px 14px;
  margin: 0;
  font-size: 0.82rem;
  color: var(--muted);
}

.thread-tech dt {
  font-weight: 500;
}

.thread-tech dd {
  margin: 0;
}

.thread-note {
  margin: 14px 0 0;
  font-size: 0.84rem;
}

/* Sender-domain hints. A known free provider says nothing about who is
   asking and is left off the card entirely; a company keeps its domain,
   written the way the request page writes it. */
.sender-domain-throwaway {
  color: var(--red);
  font-weight: 600;
}

.sender-repeat {
  color: var(--accent-dark);
  font-weight: 600;
}

/* Seeing the letter, not imagining it. Each preview is the real document in
   a frame of the width it will be read at, so the phone column is genuinely
   390px wide rather than a scaled-down desktop. */
/* The widths are the point, so they are literal: 600 is 600 and 390 is 390.
   A grid column would size these to whatever space was left over, and a
   preview at "600px" that is actually 366px wide is worse than no preview.
   They wrap when both will not fit, rather than shrink. */
.letter-previews {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 24px;
  margin-top: 18px;
}

.letter-preview {
  flex: 0 1 auto;
  max-width: 100%;
  margin: 0;
  min-width: 0;
}

.letter-preview figcaption {
  margin: 0 0 8px;
  color: var(--ink);
  font-size: 0.9rem;
  font-weight: 560;
}

/* Height is set from the letter itself once it has loaded. The starting
   value only has to be sane for the moment before that happens, and for the
   case where the script never runs. */
.letter-preview iframe {
  display: block;
  width: 600px;
  max-width: 100%;
  height: 520px;
  border: 1px solid rgba(23, 33, 29, 0.12);
  border-radius: 12px;
  background: #fff;
}

.letter-preview-phone iframe {
  width: 390px;
}

/* Three numbers that belong to one thing, so they sit on one row rather than
   as three more fields in the column above. */
.mark-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
}

/* The forms and the letter they repaint, side by side once both fit. The
   letter column is literal for the same reason the gallery's is: a preview
   at "600px" that is actually narrower is worse than no preview. Below that
   width the live frame simply joins the flow. */
.letters-workbench {
  display: grid;
  gap: 0 24px;
  align-items: start;
}

.letters-forms {
  min-width: 0;
}

.letters-live {
  min-width: 0;
  margin: 18px 0;
}

@media (min-width: 1400px) {
  .letters-workbench {
    grid-template-columns: minmax(0, 1fr) minmax(390px, 620px);
  }

  /* Pinned while the forms scroll past, so the knob and its effect stay in
     the same glance. */
  .letters-live {
    position: sticky;
    top: 16px;
  }
}

/* Inside the presets panel the workbench sits under the chip row rather
   than between panels, so it carries its own air. */
.letters-workbench-preset {
  margin-top: 4px;
}

.letters-workbench-preset .letters-live {
  margin: 0;
}

.letters-live-registers {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0 0 10px;
}

.letters-live-registers label {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 0.9rem;
  font-weight: 560;
}

/* The global field rules set width:100% and a 48px floor, which turns a
   radio into a stadium. */
.letters-live-registers input[type="radio"] {
  width: auto;
  min-height: 0;
  min-width: 0;
  padding: 0;
  box-shadow: none;
}

/* The shelf of reply letters, as chips like the reply box shows them. A
   link rather than a button: picking one is navigation, the editor opens
   below with that letter in it. */
.preset-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
}

.preset-link {
  padding: 8px 13px;
  border: 1px solid rgba(23, 33, 29, 0.14);
  border-radius: 999px;
  color: var(--muted);
  background: transparent;
  font-size: 0.9rem;
  line-height: 1.2;
  text-decoration: none;
}

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

.preset-link-active {
  border-color: var(--ink);
  color: var(--ink);
  background: rgba(255, 255, 255, 0.72);
}

.preset-rule {
  color: var(--muted);
  font-size: 0.78rem;
}

.preset-delete {
  margin-top: 14px;
}

.letter-plain {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 20px;
  margin-top: 18px;
}

.letter-plain figure {
  margin: 0;
  min-width: 0;
}

.letter-plain figcaption {
  margin: 0 0 8px;
  color: var(--ink);
  font-size: 0.9rem;
  font-weight: 560;
}

.letter-plain pre {
  margin: 0;
  padding: 16px 18px;
  overflow-x: auto;
  border: 1px solid rgba(23, 33, 29, 0.12);
  border-radius: 12px;
  background: var(--panel-solid);
  color: var(--ink);
  font-size: 13px;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

/* The letters this request may be answered with. Chips rather than a select:
   there are never more than three, they read as a row, and the one the rules
   point at is marked as suggested rather than merely placed first. */
.reply-templates {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.reply-template {
  padding: 8px 13px;
  border: 1px solid rgba(23, 33, 29, 0.14);
  border-radius: 999px;
  color: var(--muted);
  background: transparent;
  font: inherit;
  font-size: 0.9rem;
  line-height: 1.2;
  cursor: pointer;
}

.reply-template:hover {
  border-color: rgba(23, 33, 29, 0.28);
  color: var(--ink);
}

.reply-template[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent-dark);
  background: rgba(184, 102, 47, 0.08);
  font-weight: 600;
}

.reply-template-hint {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 400;
}

.reply-template[aria-pressed="true"] .reply-template-hint {
  color: var(--accent-dark);
}

.reply-templates-none {
  margin: 0;
  color: var(--muted);
  font-size: 0.9rem;
}

/* A letter still holding a placeholder says so on the field itself, and the
   send button stays disabled while it does. */
.reply-unfilled {
  margin: 0;
  color: var(--red);
  font-size: 0.9rem;
  font-weight: 560;
}

.is-unfilled {
  border-color: var(--red);
  background: rgba(155, 77, 69, 0.05);
}

/* Picking the files, then sending them: a third of the row for the first,
   two thirds for the second. On a phone they stack, because two buttons
   sharing 320px are two buttons nobody can hit. */
.reply-actions {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: 10px;
  align-items: stretch;
}

.reply-actions > * {
  margin: 0;
  width: 100%;
}

@media (max-width: 620px) {
  .reply-actions {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The picker. A working surface, not a page: a fixed head and foot so the
   city and the tally never scroll away, and the list of files between them. */
.picker {
  /* Room enough that a city with a dozen archives is read rather than
     scrolled, and that a file name, its weight and its date sit on one line. */
  --picker-height: min(880px, calc(100vh - 40px));

  width: min(760px, calc(100vw - 32px));
  max-height: var(--picker-height);
  padding: 0;
  /* The list scrolls right up to the edge, so the corners have to clip it. */
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--panel-solid);
  color: var(--ink);
}

/* Deeper, and frosted. At a third opaque and perfectly sharp, the page behind
   stayed legible - a list of cities went on being read through the dialog
   that had just been opened on one of them. The blur puts it out of reach
   without hiding that it is still there underneath. */
.picker::backdrop {
  background: rgba(23, 33, 29, 0.52);
  -webkit-backdrop-filter: blur(16px) saturate(0.92);
  backdrop-filter: blur(16px) saturate(0.92);
}

.picker-form {
  display: flex;
  max-height: var(--picker-height);
  flex-direction: column;
}

.picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 18px 0;
}

.picker-head h2 {
  margin: 0;
}

.picker-close {
  min-width: 40px;
  min-height: 40px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.picker-where {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 6px 18px 12px;
  font-weight: 600;
}

.picker-change {
  border: 0;
  background: transparent;
  color: var(--accent-dark);
  font: inherit;
  font-weight: 400;
  cursor: pointer;
}

.picker-filter {
  display: block;
  padding: 0 18px 12px;
}

.picker-filter input {
  min-height: 38px;
  padding: 7px 12px;
  border-radius: 10px;
  font-size: 0.94rem;
}

/* This field takes the focus the moment the picker opens, so both focus marks
   show at once - the accent border and the global ring three pixels outside
   it - and two orange outlines around an untouched box read as a complaint.
   The border alone still says where the typing will go. */
.picker-filter input:focus-visible {
  outline: none;
}

/* min-height: 0 or the list refuses to shrink below its content and pushes
   the Queue delivery button off the bottom of the dialog.

   The scrollbar is thinned and its track made transparent: the default one
   painted a grey band down the whole right edge of the list, which read as a
   piece of the design rather than as a control. */
.picker-body {
  min-height: 0;
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  scrollbar-width: thin;
  scrollbar-color: rgba(23, 33, 29, 0.22) transparent;
}

.picker-body::-webkit-scrollbar {
  width: 6px;
}

.picker-body::-webkit-scrollbar-track {
  background: transparent;
}

.picker-body::-webkit-scrollbar-thumb {
  border-radius: 3px;
  background: rgba(23, 33, 29, 0.22);
}

.picker-file,
.picker-city {
  display: flex;
  width: 100%;
  min-height: 40px;
  align-items: center;
  gap: 12px;
  padding: 6px 0;
  border: 0;
  border-bottom: 1px solid rgba(23, 33, 29, 0.07);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The stylesheet dresses every input as a text field - full width, 48 tall,
   padded and bordered. A tick box wants none of that: left alone it grew to
   fill the row and shrank differently on every line, so the names sat in a
   ragged staircase instead of a column. */
.picker-file input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  accent-color: var(--accent);
}

.picker-file:last-child,
.picker-city:last-child {
  border-bottom: 0;
}

/* The rule above sets display on these, and that outranks the browser's own
   [hidden] { display: none } - so a row the filter had hidden stayed on
   screen. Cities never disappeared at all; files only seemed to, because a
   whole format block would vanish when every row in it failed to match. */
.picker-file[hidden],
.picker-city[hidden] {
  display: none;
}

.picker-file-name {
  min-width: 0;
  flex: 1 1 auto;
  overflow-wrap: anywhere;
}

/* The format segment inside the name. Every row in a folder agrees on all the
   other letters, so that one segment carries the whole choice and the eye
   should be able to run down the column reading only it. */
.picker-file-name strong {
  font-weight: 600;
}

.picker-city {
  justify-content: space-between;
  font-weight: 560;
}

/* The size and the file count are the same kind of aside - small, grey, and
   never the thing you read first. Right-aligned in a column of their own, so
   the eye can compare them without reading each one. */
.picker-file-size {
  min-width: 62px;
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  text-align: right;
}

/* A delivery is two columns, not a stack: the place and its link read down the
   left, the numbers line up down the right. Stacked, every row was five lines
   tall and the middle of a wide screen stood empty. */
.dl-main,
.dl-side {
  display: grid;
  gap: 2px;
  align-content: start;
  min-width: 0;
}

.dl-side {
  justify-items: end;
  text-align: right;
  white-space: nowrap;
}

/* As wide as its own words. A grid item stretches across its column, and the
   link took every click on the left half of the row - including the ones
   meant to open the delivery. */
.dl-main a {
  justify-self: start;
  overflow-wrap: anywhere;
}

/* The one line on the row that is worth acting on, so it is not grey like the
   weights and the dates around it. */
.dl-outcome {
  color: var(--ink);
}

.app-main-files .timeline li {
  padding: 10px 0;
}

/* A delivery someone has downloaded opens, on a click anywhere in the row,
   onto who took it - the Insights queue's fold, its chevron and its hover.
   Every row keeps the chevron's column whether it opens or not, so the
   numbers still line up down the list. */
.timeline .dl-row,
.dl-row > .dl-fold > summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto 7px;
  gap: 4px 12px;
}

.dl-row > .dl-fold {
  grid-column: 1 / -1;
}

.dl-row > .dl-fold > summary {
  padding-right: 0;
}

/* The rows past the first twenty, and "More" once they are all out. The grid
   above and the button's own display both outrank the page-wide [hidden]
   rule, as the picker's display did for its rows. */
.timeline .dl-row[hidden],
.dl-more[hidden] {
  display: none;
}

/* Who took it: the shelf's own rows, one per address - who and from where,
   then how it ended and when. Set in from the edge of the delivery so they
   read as its contents rather than as more deliveries, and stopping where the
   numbers above them stop, short of the chevron. */
.dl-takers {
  margin: 0 19px 4px 11px;
}

/* Not a button here: nothing happens on a click. */
.dl-takers .picker-city {
  cursor: default;
}

.dl-more {
  margin-top: 12px;
}

@media (max-width: 620px) {
  /* One column on a phone, as every timeline is, with the chevron kept to
     the right of the stack. */
  .timeline .dl-row,
  .dl-row > .dl-fold > summary {
    grid-template-columns: minmax(0, 1fr) 7px;
  }

  .dl-row .dl-side {
    grid-column: 1;
  }

  .dl-row .queue-chevron {
    grid-column: 2;
    grid-row: 1 / span 2;
  }

  /* Who on a line of their own, how it ended and when on the next - across
     the whole width, since on a phone the chevron has a column of its own
     only beside the delivery, not beside what it opens onto. */
  .dl-takers {
    margin-right: 0;
  }

  .dl-takers .picker-city {
    flex-wrap: wrap;
  }

  .dl-takers .picker-file-name {
    flex-basis: 100%;
  }

  /* A long ending pushes the time to a line of its own; it keeps to the
     right there too, where the other rows have it. */
  .dl-takers .picker-file-date {
    margin-left: auto;
  }
}

/* The search sits between the heading and the first panel and is not a panel
   itself: a card around a single field would say this is a section of the
   page, when it is the way into the rest of it. A pill, because everything
   here that is pressed or typed into is one, and because this is the widest
   of them - the shape is what says it comes first. */
.files-search {
  position: relative;
  margin: 0 0 20px;
}

.files-search-field {
  position: relative;
  display: flex;
  align-items: center;
}

.files-search-glyph {
  position: absolute;
  left: 21px;
  width: 19px;
  height: 19px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.75;
  stroke-linecap: round;
  pointer-events: none;
}

/* Bigger than a form field and set a little tighter: this is read as a line
   of the page rather than filled in as part of a form, and a city name is
   two or three words that should sit in it comfortably. */
.files-search input {
  min-height: 56px;
  padding: 14px 22px 14px 52px;
  border-radius: 999px;
  font-size: 1.06rem;
  font-weight: 500;
  letter-spacing: -0.018em;
}

.files-search input::placeholder {
  color: var(--muted);
  font-weight: 450;
  letter-spacing: -0.01em;
}

/* No ring. The global one is a second outline two pixels outside the field's
   own, and around a pill that reads as a mistake rather than as focus - the
   same complaint the filter bar and the picker's own box already answer. The
   caret and the answers underneath say where the typing is going. */
.files-search input:focus-visible {
  outline: none;
}

/* Safari draws its own clear button inside a search field. In a pill it sits
   on the curve, and Escape already empties the field. */
.files-search input::-webkit-search-decoration,
.files-search input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* The answers hang off the field rather than pushing the page: a second
   letter should not shove the tally and the shelves down a screen and back
   up again. Opaque, because it is lying over them.

   As tall as the window allows. It was capped at a third of that, which cut
   the list off mid-name and made a search that had found forty cities look
   like one that had found seven. */
.files-search-results {
  position: absolute;
  z-index: 5;
  top: calc(100% + 8px);
  right: 0;
  left: 0;
  max-height: min(66vh, 620px);
  padding: 8px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: 22px;
  background: var(--panel-solid);
  box-shadow: 0 18px 44px rgba(23, 33, 29, 0.13);
  overscroll-behavior: contain;
  scrollbar-width: none;
}

/* No scrollbar at all. It is drawn inside the card, so it took a dozen pixels
   off the right edge that the left edge never gave up - the rows sat off
   centre - and it painted over the rounded corners on that side, squaring
   them. The list still scrolls; the arrow keys already carry the chosen row
   into view, and a name cut by the bottom edge says there is more below it
   more plainly than a bar ever did. */
.files-search-results::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/* No hairlines between the answers. They divide a list you read down; these
   are a handful of things to pick one of, and a rule under a rounded
   highlight cuts across it. */
.files-search-results .picker-city {
  min-height: 40px;
  padding: 5px 12px;
  border-bottom: 0;
  border-radius: 11px;
}

/* Where the arrow keys are. The same tint the format blocks use, so it reads
   as "this row", not as a second kind of selection. */
.files-search-results .picker-city[data-active] {
  background: rgba(23, 33, 29, 0.05);
}

.files-search-results .picker-empty {
  margin: 10px 12px;
}

/* Two shelves side by side, and neither waits for the other: the columns are
   independent panels, so ten releases next to three samples leaves no hole.
   One under the other on anything narrower - side by side, a folder name with
   a date after it would have nowhere to go. */
.shelf-columns {
  display: grid;
  gap: 0 18px;
  align-items: start;
}

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

/* The rows are the picker's city rows, because that is what they are: a folder
   with what is in it written after the name. The date is the reason the ten
   are the ten, so it keeps a column of its own. */
.shelf-list .picker-city {
  gap: 10px;
}

/* When the file was built, after its weight. Choosing between two archives of
   one city is usually a question of which one is newer. Shown only where the
   date means something: the backup keeps real timestamps, and the sample
   shelf - whose own file dates the bulk upload rewrote - reads the stamps
   inside the archive. A picture shows none. */
.picker-file-date {
  min-width: 96px;
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
  font-weight: 400;
  text-align: right;
}

/* The people list. One card per person, four ranks of fact inside it, each
   in its own place - who, where from, which city, what they did.

   Two earlier cuts failed in opposite directions. Cards carrying a name and
   a date on a row a thousand pixels wide, with the one fact the hub exists
   for - which city - missing entirely. Then dense rows, which fitted more
   people and ran the facts together: "wrote 7 letters · back on 6 days ·
   waiting 14 days" is three different kinds of statement in one grey
   sentence, and none of them is read.

   The figures below are the Statistics page's own stat tiles (see
   `.stat-tile`), sized down until a row of four fits a phone. */
.people-list {
  display: grid;
  gap: 10px;
  margin-top: 16px;
}

.person-card {
  display: grid;
  gap: 6px;
  align-content: start;
  padding: 14px 16px 15px;
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--panel);
  box-shadow: var(--shadow);
  color: inherit;
  text-decoration: none;
}

.person-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.person-name {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.012em;
}

.person-when {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.person-from {
  color: var(--muted);
  font-size: 0.86rem;
}

/* The city, which is the whole of what they want. Set as the subject of the
   card rather than as another grey aside. */
.person-places {
  font-weight: 560;
  overflow-wrap: anywhere;
}

.person-more {
  color: var(--muted);
  font-weight: 400;
}

.person-marks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin-top: 2px;
}

.person-mark {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  padding: 3px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.5);
  font-size: 0.79rem;
}

.person-mark strong {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.person-mark span {
  color: var(--muted);
}

/* What we owe, which is not one of their figures. Outlined rather than
   filled: forty-four terracotta pills down a column is an alarm nobody can
   act on, and the colour has to still mean something on the two or three
   cards where it is the point. */
.person-mark-debt {
  border-color: rgba(184, 102, 47, 0.4);
  color: var(--accent-dark);
  background: transparent;
  font-weight: 540;
}

/* Waited a while. A fact, not an alarm: it is true of nearly everyone in
   the queue, and a colour that is on every card carries nothing. */
.person-mark-quiet {
  border-color: transparent;
  background: transparent;
  color: var(--muted);
  padding-left: 2px;
  padding-right: 2px;
}

/* The operator's own word, and the only mark that is filled. */
.person-mark-due {
  border-color: transparent;
  color: #71370e;
  background: #f7dfc6;
  font-weight: 560;
}

/* Given room, the card is two blocks rather than four stacked lines: who
   and what they want down the left, what they have done down the right.
   Stacked at full width it left a third of every card empty and put the
   figures a long way under the name they belong to. */
@media (min-width: 860px) {
  .person-card {
    grid-template-columns: minmax(0, 1fr) auto;
    column-gap: 24px;
    align-items: start;
  }

  .person-head,
  .person-from,
  .person-places {
    grid-column: 1;
  }

  .person-marks {
    grid-column: 2;
    grid-row: 1 / span 3;
    align-self: center;
    justify-content: flex-end;
    margin-top: 0;
  }

  .person-when {
    position: absolute;
  }

  .person-card {
    position: relative;
  }

  .person-head {
    justify-content: flex-start;
  }

  .person-when {
    top: 15px;
    right: 16px;
  }
}

/* One card per format. The tint and the corners do the grouping, so the gap
   between cards can stay small - the list has to hold a dozen archives
   without turning into a scroll. */
.picker-group {
  padding: 0 12px;
  border-radius: 12px;
  background: rgba(23, 33, 29, 0.035);
}

.picker-group + .picker-group {
  margin-top: 7px;
}

/* Inside a card the hairlines only ever divide files of the same kind, and
   the last one would sit on the card's own edge. */
.picker-group > .picker-file:last-child {
  border-bottom: 0;
}

.picker-empty {
  margin: 18px 0;
  color: var(--muted);
}

.picker-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 18px 18px;
}

.picker-tally {
  margin: 0;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.picker-foot .primary-button {
  margin: 0;
  flex: 0 0 auto;
}

/* Name and company share the heading, so they travel together and the unread
   dot keeps the far edge to itself. They wrap rather than squeeze. */
.request-card-who {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  min-width: 0;
}

/* The gap does the spacing on both sides, so the bullet only has to be there
   and to stay out of the way. */
.request-card-sep {
  color: var(--muted);
  font-weight: 400;
}

/* The heading is set in 600; the company is a link beside it, not part of the
   name, so it drops back to normal weight. No rule under it - the arrow says
   it leads somewhere, and a line here would fight the heading. Raised above
   the card sheet, being the one thing that goes elsewhere than the request. */
.request-card-who .contact-line {
  position: relative;
  z-index: 2;
  font-weight: 400;
  text-decoration: none;
}

@media (max-width: 620px) {
  /* Two columns leave the right-hand summary nowhere to go on a phone, and it
     was being cut off at the edge. One column, stacked. */
  .timeline li {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Delivery state on a request card. Three weights, deliberately unequal: the
   broken transfer is the only one that needs to catch the eye across a list. */
/* The header is space-between on two children, so a third lands dead centre.
   Pushing the free space to its right keeps it beside the status it belongs
   to, and leaves the time where it was. */
.request-card-top .delivery-done,
.request-card-top .delivery-waiting,
.request-card-top .delivery-broken {
  margin-right: auto;
}

/* Only when nothing follows it: a second auto margin in the same flex row
   detaches the delivery pill and floats it into the middle of the card. */
.request-card-top .payment-paid:last-child {
  margin-right: auto;
}

/* Filled, like the broken one it stands beside. A tinted pill among solid
   ones reads as the lesser state, and arriving in full is not lesser - it is
   the answer being looked for. */
/* On a narrow screen the delivery pills collapse to their icon. The payment
   pill has no icon - four characters of money say it better than a glyph - so
   it keeps its words at every width. */
@media (max-width: 620px) {
  .status-pill.payment-paid .delivery-text {
    display: inline;
  }
}

/* Money arrived. It reads as the strongest state on the card on purpose:
   everything else on this row is about work owed, this one is about work
   already bought. */
.status-pill.delivery-done,
.status-pill.payment-paid {
  border-color: transparent;
  background: var(--green);
  color: #fff;
}

.status-pill.payment-paid {
  font-variant-numeric: tabular-nums;
}

/* Between the two: files were handed over and nobody came for them. Not a
   failure, but the one state on the card that might need you tomorrow, so it
   carries the accent rather than disappearing into grey. */
.status-pill.delivery-waiting {
  border-color: rgba(184, 102, 47, 0.32);
  background: rgba(184, 102, 47, 0.12);
  color: var(--accent-dark);
}

.status-pill.delivery-broken {
  border-color: transparent;
  background: var(--red);
  color: #fff;
}

/* What is still owed, beside the badge that says what arrived. Tinted, not
   filled: a debt of one format is not a failed download, and the red pill has
   to keep meaning only that. The unsent one is filled instead - those files
   exist and are sitting here, which is the operator's own move to make. */
.status-pill.delivery-owed {
  border-color: rgba(184, 102, 47, 0.32);
  background: rgba(184, 102, 47, 0.12);
  color: var(--accent-dark);
}

.status-pill.delivery-owed.is-unsent {
  border-color: transparent;
  background: var(--accent-dark);
  color: #fff;
}

/* The free space belongs to the right of the last pill in the row, so the
   date column stays true whether the debt is there or not. */
.request-card-top .delivery-done:has(+ .delivery-owed),
.request-card-top .delivery-waiting:has(+ .delivery-owed),
.request-card-top .delivery-broken:has(+ .delivery-owed) {
  margin-right: 0;
}

.request-card-top .delivery-owed {
  margin-right: auto;
}

.status-alarm {
  color: var(--red);
}

@media (max-width: 720px) {
  .journal-row {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }
}

/* One request, one entry. Read down the left edge and you get names, not
   event types: the eye is looking for a person, not for a category. */
.episodes {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.episode {
  padding: 15px 0 15px 13px;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
}

.episode-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-bottom: 5px;
}

.episode-who {
  color: var(--ink);
  font-size: 1.02rem;
  font-weight: 560;
  text-decoration: none;
}

.episode-who:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.episode-anon {
  color: var(--muted);
  font-weight: 500;
}

.episode-where {
  color: var(--muted);
}

.episode-gone {
  color: var(--muted);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Relative, because "4 hours ago" answers the question and a timestamp makes
   you do arithmetic. The exact moment is in the tooltip. */
.episode-ago {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.84rem;
  white-space: nowrap;
}

.episode-line,
.episode-outcome,
.episode-files,
.episode-foot {
  margin: 2px 0 0;
  font-size: 0.9rem;
}

.episode-line {
  color: var(--muted);
}

.episode-outcome {
  color: var(--ink);
}

.episode-files,
.episode-foot {
  color: var(--muted);
  font-size: 0.8rem;
}

.episode-foot a {
  color: inherit;
  font-variant-numeric: tabular-nums;
}

/* Only two states are coloured: the one that needs you now, and the one that
   might tomorrow. Everything else stays quiet, or none of it would carry. */
.episode-alarm {
  border-left-color: var(--red);
}

.episode-alarm .episode-outcome {
  color: var(--red);
  font-weight: 520;
}

.episode-warn {
  border-left-color: rgba(184, 102, 47, 0.5);
}

.episode-ok .episode-outcome {
  color: var(--green);
}

.status-alarm {
  color: var(--red);
}

/* One request, one row, and every fact in a column of its own. Prose is read
   line by line; a column is scanned - the eye runs down Outcome and stops
   where it is wrong. */
.table-scroll {
  overflow-x: auto;
}

.rows {
  display: grid;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}

.rows-table {
  min-width: 44rem;
}

.row {
  display: grid;
  align-items: baseline;
  gap: 14px;
  padding: 9px 0 9px 11px;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
  font-size: 0.88rem;
}

.rows-table .row {
  grid-template-columns:
    minmax(7rem, 1.3fr) minmax(6rem, 1.1fr) minmax(6rem, 1.3fr)
    8.5rem 10.5rem 5.5rem;
}

.rows-needs .row {
  grid-template-columns: minmax(7rem, 1.3fr) minmax(6rem, 1.1fr) 10.5rem 5.5rem;
}

/* The build list on Statistics: place, formats, shelf, what happened, waiting.
   The middle column is the widest because it carries a sentence. */
.rows-build .row {
  grid-template-columns:
    minmax(7rem, 1.1fr) minmax(5rem, 0.8fr) minmax(8rem, 1fr)
    minmax(10rem, 1.5fr) 6rem;
}

/* Two counts against a name - formats, and places. */
.rows-pair .row {
  grid-template-columns: minmax(8rem, 2fr) 9rem 12rem;
}

/* A door, its visitors, its share, its movement - the bulletin's tables. */
.rows-doors .row {
  grid-template-columns: minmax(9rem, 2fr) 6rem 6.5rem 6rem;
}

.rows-build,
.rows-pair {
  min-width: 40rem;
}

/* An invoice's lines: the description takes the width, three numbers hang
   off the right edge. The form that writes them is the same three columns
   with inputs in them. */
.rows-invoice .row {
  grid-template-columns: minmax(12rem, 4fr) 4rem 7rem 8rem;
}

.rows-invoice .num,
.invoice-line-head span:nth-child(n + 2) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.rows-invoice .invoice-total {
  border-top: 1px solid var(--ink);
}

.rows-invoice.rows {
  min-width: 0;
}

.invoice-letter {
  grid-template-columns: minmax(10rem, 2fr) minmax(8rem, 1.5fr) auto;
}

.invoice-lines {
  display: grid;
  gap: 8px;
}

.invoice-line {
  display: grid;
  grid-template-columns: minmax(12rem, 4fr) 4rem 7rem;
  gap: 8px;
}

.invoice-line-head {
  font-size: 0.78rem;
  color: var(--dim);
}

@media (max-width: 620px) {
  .invoice-line {
    grid-template-columns: 1fr;
  }

  .invoice-line-head {
    display: none;
  }
}

.rows-doors {
  min-width: 34rem;
}

.row-head {
  border-top: 0;
  color: var(--muted);
  font-size: 0.74rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.row > span,
.row > a,
.row > time {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.row-who {
  color: var(--ink);
  font-weight: 540;
  text-decoration: none;
}

a.row-who:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.row-anon,
.row-where,
.row-asked,
.row-package {
  color: var(--muted);
}

.row > time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.row-gone .row-who {
  color: var(--muted);
  text-decoration: line-through;
}

/* Two states are coloured and no more. A third would start the slide back to
   a page where everything is highlighted and nothing stands out. */
.row-alarm {
  border-left-color: var(--red);
}

.row-alarm .row-outcome {
  color: var(--red);
  font-weight: 540;
}

.row-warn {
  border-left-color: rgba(184, 102, 47, 0.55);
}

.row-warn .row-outcome {
  color: var(--accent);
}

.row-ok .row-outcome {
  color: var(--green);
}

.status-alarm {
  color: var(--red);
}

/* The handover record. Full width at the bottom: file names are long, and this
   is the answer to "did they actually get it". */
.parcel-files {
  display: grid;
  gap: 0;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.9rem;
}

.parcel-files li {
  padding: 7px 0;
  border-top: 1px solid var(--line);
  overflow-wrap: anywhere;
}

.parcel-outcome {
  margin: 14px 0 0;
  font-size: 0.92rem;
}

.parcel-ok {
  color: var(--green);
}

.parcel-warn {
  color: var(--accent);
}

.parcel-alarm {
  color: var(--red);
  font-weight: 520;
}

/* A broken transfer in the timeline. The only coloured line there. */
.activity-alarm strong,
.activity-alarm span {
  color: var(--red);
}

/* The eyebrow, the status and the field labels are the same rank of small
   print, so they are set at the same size. They were 0.71, 0.76 and 0.78rem -
   three near-misses read as a wobble rather than as a hierarchy. */
.detail-heading .eyebrow,
.detail-heading .status-pill {
  font-size: 0.78rem;
}

/* The filing code, demoted from the title but still one glance away. */
.heading-reference {
  margin-left: 12px;
  color: var(--muted);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
}

/* Insights: the decision queue. One city, one line - date, count, extent,
   airport, and the writer's note cut at the row's edge. The chevron at the
   end unfolds the full note under the same line; every fact above it stays
   where it was. Set a step lighter than the request tables: this page is
   read once a week, not worked in. */
.rows-queue {
  min-width: 50rem;
}

.rows-queue .row {
  grid-template-columns:
    minmax(6.5rem, 1fr) 6.4rem 4rem 6.8rem
    minmax(7.5rem, 1.1fr) minmax(12rem, 2.6fr) 1.4rem;
}

.rows-empty {
  min-width: 42rem;
}

.rows-empty .row {
  grid-template-columns: minmax(6.5rem, 1fr) 7rem minmax(14rem, 3.4fr) 1.4rem;
}

.rows-queue .row,
.rows-empty .row,
.queue-full,
.app-main-insights table {
  font-size: 0.85rem;
  font-weight: 350;
  letter-spacing: 0.002em;
}

.rows-queue .row-head,
.rows-empty .row-head,
.app-main-insights thead th {
  font-size: 0.71rem;
  font-weight: 430;
  letter-spacing: 0.07em;
}

.rows-queue .row-who,
.rows-empty .row-who,
.app-main-insights td strong {
  font-weight: 490;
}

.queue-fold > summary {
  padding-right: 12px;
  cursor: pointer;
  list-style: none;
}

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

.queue-fold > summary:hover {
  background: rgba(23, 33, 29, 0.03);
}

.queue-count {
  font-variant-numeric: tabular-nums;
}

.queue-count,
.queue-fact,
.queue-note {
  color: var(--muted);
}

.queue-note {
  transition: opacity 0.18s ease;
}

/* Open, the one-line preview yields to the full text below it - the same
   words twice would read as a stutter. The columns keep their places. */
.queue-fold[open] .queue-note {
  opacity: 0;
}

.queue-chevron {
  align-self: center;
  justify-self: end;
  width: 7px;
  height: 7px;
  border-right: 1.4px solid var(--muted);
  border-bottom: 1.4px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.22s ease;
}

.queue-fold[open] > summary .queue-chevron {
  transform: rotate(45deg);
}

.queue-full {
  margin: 0;
  padding: 0 24px 14px 11px;
  max-width: 68ch;
  color: var(--ink);
  line-height: 1.55;
}

/* The rest of the page's tables - airports, the calendar, the numbers - on
   the same quiet setting: hairlines and small print, no chrome. */
.app-main-insights table {
  width: 100%;
  border-collapse: collapse;
}

.app-main-insights th,
.app-main-insights td {
  padding: 8px 14px 8px 0;
  border-top: 1px solid var(--line);
  text-align: left;
  vertical-align: baseline;
}

.app-main-insights thead th {
  border-top: 0;
  color: var(--muted);
  text-transform: uppercase;
}

.app-main-insights tbody th {
  color: var(--muted);
  font-weight: 430;
  white-space: nowrap;
}

.insights-gap-fold > summary {
  cursor: pointer;
  color: var(--muted);
}

/* The number on the mail glyph. It sits on the icon rather than beside the
   label because on a phone there is no label to sit beside, and it is the
   one thing in the bar that must be readable without reading. */
.nav-count {
  position: absolute;
  z-index: 2;
  top: 3px;
  right: 3px;
  display: grid;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  place-items: center;
  border-radius: 999px;
  color: #fff;
  background: var(--accent-dark);
  font-size: 0.66rem;
  font-weight: 660;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.nav-count[hidden] {
  display: none;
}

/* A letter has no ••• button, so the room a request card keeps on its right
   for one is just a hole. */
.mail-card {
  padding-right: 18px;
}

/* The letter as it was written. The sender's line breaks carry meaning - a
   numbered list of questions reflowed into a paragraph stops being a list. */
.mail-body {
  margin: 10px 0 0;
  line-height: 1.55;
  white-space: pre-wrap;
}

/* The half the parser cut off, offered rather than dropped. Quiet by default:
   it is there to be checked when something looks missing, not to be read. */
.mail-trimmed {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.mail-trimmed summary {
  color: var(--muted);
  font-size: 0.86rem;
  cursor: pointer;
  list-style: none;
}

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

.mail-trimmed summary::before {
  content: "▸ ";
}

.mail-trimmed[open] summary::before {
  content: "▾ ";
}

.mail-trimmed .mail-body {
  color: var(--muted);
  font-size: 0.9rem;
}

/* A link inside a letter. Underlined rather than coloured alone, because the
   letter is grey-on-paper prose and colour by itself would not carry. Long
   URLs break instead of pushing the card wide. */
.mail-body a {
  color: var(--accent-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: anywhere;
}

.mail-trimmed .mail-body a {
  color: inherit;
}

/* The secretary's weekly ledger. Numbers in columns, hairlines between rows,
   nothing that competes with the letters above it. */
.mail-ledger-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
  font-variant-numeric: tabular-nums;
}

.mail-ledger-table th {
  padding: 4px 8px;
  color: var(--muted);
  font-size: 0.71rem;
  font-weight: 620;
  letter-spacing: 0.08em;
  text-align: right;
  text-transform: uppercase;
}

.mail-ledger-table td {
  padding: 7px 8px;
  border-top: 1px solid var(--line);
  text-align: right;
}

.mail-ledger-table th:first-child,
.mail-ledger-table td:first-child {
  padding-left: 0;
  text-align: left;
}

.mail-ledger-total td {
  font-weight: 600;
}

.mail-ledger-headline {
  margin: 4px 0 10px;
  font-size: 1.02rem;
}

.mail-ledger-headline strong {
  font-size: 1.35rem;
  font-weight: 640;
  letter-spacing: -0.02em;
}

/* The triage panel. The verdict reads as pills, the summary as prose, the
   questions as a numbered list you answer in one line underneath. */
.mail-verdict {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 10px;
}

.mail-summary {
  margin: 0 0 12px;
  font-size: 1.02rem;
  line-height: 1.5;
  white-space: pre-wrap;
}

.mail-questions {
  margin: 0 0 14px;
  padding-left: 22px;
}

.mail-questions li {
  margin: 4px 0;
  line-height: 1.45;
}

.mail-triage textarea {
  font-size: 1rem;
}

/* The drafted reply. Monospaced would be wrong - this is prose about to be
   sent to a person - but it is editable, so it reads as a field. */
.mail-draft-text {
  width: 100%;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel-solid);
  font-family: inherit;
  font-size: 0.97rem;
  line-height: 1.5;
  resize: vertical;
}

.mail-draft-actions {
  margin-top: 12px;
}

.mail-unresolved {
  margin: 6px 0 0;
  padding-left: 20px;
}

/* The way up. On a phone every section root carries one link above its
   heading, to the hub screen; a page inside a section names its section
   instead. The same chevron, the same weight, whichever it is, so the eye
   learns the shape once. On a desktop the sidebar is the level above and
   the hub link is not drawn; the section links stay, they lead within. */
.hub-return,
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  min-height: 44px;
  margin: -6px 0 2px -8px;
  padding: 0 10px 0 4px;
  border-radius: 12px;
  color: var(--accent-dark);
  font-weight: 600;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: opacity 120ms ease;
}

.hub-return svg,
.back-link svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.1;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.hub-return:active,
.back-link:active {
  opacity: 0.5;
  transition-duration: 40ms;
}

@media (min-width: 1000px) {
  .hub-return {
    display: none;
  }
}

/* The hub screen. A page above the sections, for a phone: what a bottom bar
   could not hold - eight sections instead of four, and what is waiting in
   each - on a screen that is left the moment a section is chosen. Grouped
   the way the desktop rail is read: the work, the reading, the machine,
   the account. */
.app-main-menu .page-heading {
  margin-bottom: 26px;
}

.menu-summary {
  margin: 4px 0 0;
  font-size: 0.95rem;
}

.menu-group {
  margin: 0 0 22px;
}

.menu-group-title {
  margin: 0 0 8px 16px;
  color: var(--muted);
  font-size: 0.71rem;
  font-weight: 620;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.menu-list {
  margin: 0;
  padding: 0;
  overflow: hidden;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: 18px;
  background: var(--panel-solid);
  box-shadow: var(--shadow);
}

.menu-row {
  position: relative;
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center;
  gap: 13px;
  min-height: 62px;
  padding: 10px 12px 10px 13px;
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: background-color 140ms ease;
}

/* The rule between rows starts where the words do, not at the edge: the
   glyphs stand in their own column and the separator belongs to the text. */
.menu-list li + li .menu-row::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 66px;
  border-top: 1px solid var(--line);
}

.menu-row:active {
  background: rgba(23, 33, 29, 0.05);
  transition-duration: 40ms;
}

@media (hover: hover) {
  .menu-row:hover {
    background: rgba(23, 33, 29, 0.035);
  }
}

.menu-glyph {
  display: grid;
  width: 40px;
  height: 40px;
  place-items: center;
  border-radius: 12px;
  color: var(--ink);
  background: rgba(23, 33, 29, 0.07);
}

.menu-glyph svg {
  width: 21px;
  height: 21px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.65;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The three working sections keep a tint of their own, faint enough to stay
   paper; the reading and the machine are the colour of the page. */
.menu-row[data-section="requests"] .menu-glyph {
  color: var(--accent-dark);
  background: rgba(184, 102, 47, 0.13);
}

.menu-row[data-section="mail"] .menu-glyph {
  color: var(--blue);
  background: rgba(49, 91, 125, 0.12);
}

.menu-row[data-section="files"] .menu-glyph {
  color: var(--green);
  background: rgba(61, 111, 88, 0.13);
}

.menu-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.menu-title {
  font-size: 1rem;
  font-weight: 560;
  letter-spacing: -0.01em;
}

.menu-sub {
  display: flex;
  align-items: center;
  gap: 7px;
  overflow: hidden;
  color: var(--muted);
  font-size: 0.82rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-end {
  display: flex;
  align-items: center;
  gap: 10px;
}

.menu-count {
  display: grid;
  min-width: 26px;
  height: 26px;
  padding: 0 9px;
  place-items: center;
  border-radius: 999px;
  color: #fff;
  background: var(--accent-dark);
  font-size: 0.78rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.menu-count[hidden] {
  display: none;
}

.menu-count.is-warn {
  background: var(--accent);
}

.menu-count.is-alarm {
  background: var(--red);
}

.menu-chevron {
  display: grid;
  place-items: center;
  color: rgba(23, 33, 29, 0.28);
}

.menu-chevron svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The Status row's answer: a dot for the level, a phrase for the reason.
   The dot breathes while the answer is still on its way. */
.menu-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: rgba(23, 33, 29, 0.22);
  animation: menu-dot-wait 1.4s ease-in-out infinite;
}

.menu-dot.is-ok,
.menu-dot.is-warn,
.menu-dot.is-alarm,
.menu-dot.is-unknown {
  animation: none;
}

.menu-dot.is-ok {
  background: var(--green);
}

.menu-dot.is-warn {
  background: var(--accent);
}

.menu-dot.is-alarm {
  background: var(--red);
  box-shadow: 0 0 0 3px rgba(155, 77, 69, 0.18);
}

@keyframes menu-dot-wait {
  50% {
    opacity: 0.35;
  }
}

.menu-signout-page {
  margin: 30px 0 0;
}

.menu-signout-page .secondary-button {
  width: 100%;
}

/* Cross-document view transitions. Which way to slide is decided by
   reveal.js before the first paint and written on the root; without a
   verdict the transition is skipped, so a reload, a filter change and the
   browser's own back - the swipe from the edge, which draws its own motion -
   never animate twice. Going in, the new page comes from the right and the
   old one recedes a little; coming back, the reverse, and the section title
   travels between the hub's row and the page's heading. */
@view-transition {
  navigation: auto;
}

::view-transition-group(*) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.22, 0.8, 0.24, 1);
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.22, 0.8, 0.24, 1);
  mix-blend-mode: normal;
}

html[data-hub-nav="forward"]::view-transition-old(root) {
  animation-name: hub-recede;
}

html[data-hub-nav="forward"]::view-transition-new(root) {
  animation-name: hub-arrive;
}

html[data-hub-nav="back"]::view-transition-old(root) {
  animation-name: hub-depart;
}

html[data-hub-nav="back"]::view-transition-new(root) {
  animation-name: hub-return;
}

@keyframes hub-arrive {
  from {
    opacity: 0;
    transform: translateX(28px);
  }
}

@keyframes hub-recede {
  to {
    opacity: 0;
    transform: translateX(-14px);
  }
}

@keyframes hub-return {
  from {
    opacity: 0;
    transform: translateX(-14px);
  }
}

@keyframes hub-depart {
  to {
    opacity: 0;
    transform: translateX(28px);
  }
}

[data-section-title="requests"] {
  view-transition-name: section-requests;
}

[data-section-title="mail"] {
  view-transition-name: section-mail;
}

[data-section-title="files"] {
  view-transition-name: section-files;
}

[data-section-title="sales"] {
  view-transition-name: section-sales;
}

[data-section-title="statistics"] {
  view-transition-name: section-statistics;
}

[data-section-title="insights"] {
  view-transition-name: section-insights;
}

[data-section-title="status"] {
  view-transition-name: section-status;
}

[data-section-title="settings"] {
  view-transition-name: section-settings;
}

@media (prefers-reduced-motion: reduce) {
  .menu-dot {
    animation: none;
  }

  ::view-transition-group(*),
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.01ms;
  }
}
