:root {
  color-scheme: light dark;

  --color-background: #f5f3ee;
  --color-surface: #ffffff;
  --color-text: #1f1f24;
  --color-text-muted: #5c5c66;
  --color-border: #dcd8d0;
  --color-accent: #b8560f;
  --color-accent-contrast: #ffffff;
  --color-accent-tint: #f8e9dc;
  --color-focus: #1d4ed8;
  --color-error: #a4262c;
  --color-error-tint: #fbe9e9;
  --color-open: #8a5a00;
  --color-open-tint: #fff3d6;
  --color-paid: #1f6b3a;
  --color-paid-tint: #dff3e6;
  --color-cancelled: #5c5c66;
  --color-cancelled-tint: #ebebef;
  --color-seen: #1a7f37;
  --color-unseen: #c62828;
  --color-reconnecting: #b26a00;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  --radius: 0.75rem;
  --tap-target: 2.75rem;
  --header-height: 3.5rem;
  --bottom-navigation-height: 3.75rem;
  --sidebar-width: 14rem;
  --content-max-width: 64rem;
  --text-measure: 72ch;
  --story-measure: 68ch;

  --font-family:
    system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
    sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-background: #17171b;
    --color-surface: #232329;
    --color-text: #ececf1;
    --color-text-muted: #a6a6b3;
    --color-border: #37373f;
    --color-accent: #f0a05a;
    --color-accent-contrast: #1f1f24;
    --color-accent-tint: #3a2a1c;
    --color-focus: #8ab4f8;
    --color-error: #f2a8a4;
    --color-error-tint: #3a2222;
    --color-open: #f5c76a;
    --color-open-tint: #3a2f16;
    --color-paid: #8fd9a8;
    --color-paid-tint: #1d3a28;
    --color-cancelled: #b5b5c0;
    --color-cancelled-tint: #2f2f36;
    --color-seen: #57d27a;
    --color-unseen: #ff8a80;
    --color-reconnecting: #f5b953;
  }
}

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  /* The column may shrink below the header's content width, so that the
     node bar scrolls within itself instead of widening the page. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    'header'
    'main'
    'navigation';
  font-family: var(--font-family);
  font-size: 1rem;
  line-height: 1.5;
  color: var(--color-text);
  background: var(--color-background);
  overflow-wrap: anywhere;
}

:focus-visible {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

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

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

.app-header {
  grid-area: header;
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  height: var(--header-height);
  padding: 0 var(--space-4);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.app-title {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  font-size: 1.125rem;
  font-weight: 700;
  color: var(--color-text);
  text-decoration: none;
  white-space: nowrap;
}

/*
 * The node bar lists every node of the environment next to the title. From
 * 768 px the header has room for a row of nodes; should it still overflow,
 * the bar scrolls sideways within itself instead of the page growing wider,
 * and the active badge comes first so that it always stays in view. Below
 * 768 px a bar with other nodes takes a row of its own under the title and
 * wraps, so that every node is visible without scrolling (see the phone
 * rule further down).
 */
.node-bar {
  /* Positioned so that the visually hidden labels inside the links are
     clipped with the bar instead of sticking out of the sticky header. */
  position: relative;
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  height: 100%;
  overflow-x: auto;
  scrollbar-width: thin;
}

/*
 * Right-aligned while there is room; `justify-content: flex-end` would
 * push an overflowing bar's start, the active badge, into the unscrollable
 * side, whereas an auto margin collapses to zero once the bar overflows.
 */
.node-bar > :first-child {
  margin-left: auto;
}

@media (max-width: 767.98px) {
  .app-header {
    flex-wrap: wrap;
    row-gap: 0;
    height: auto;
    min-height: var(--header-height);
  }

  /*
   * The bar's height follows its content here, never the header's: a bar
   * that wrapped under the title (a long node name, a wide font) would
   * otherwise be as tall as the whole header and hang over the page.
   */
  .node-bar {
    height: auto;
    padding: var(--space-1) 0;
  }

  .node-bar:has(.node-partner) {
    flex-basis: 100%;
    flex-wrap: wrap;
    padding-bottom: var(--space-2);
    overflow: visible;
  }

  .node-bar:has(.node-partner) > :first-child {
    margin-left: 0;
  }
}

/*
 * The live updates indicator sits next to the title: a dot in the colour
 * of the connection state (green live, amber while (re)connecting, grey
 * offline) and the state as a word, so that the colour never stands
 * alone. It never shrinks or wraps; the node bar next to it takes the
 * remaining room.
 */
.live-indicator {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--tap-target);
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.live-dot {
  display: inline-block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--color-text-muted);
}

.live-indicator-live .live-dot {
  background: var(--color-seen);
}

.live-indicator-reconnecting .live-dot {
  background: var(--color-reconnecting);
}

.node-badge,
.node-partner {
  display: inline-flex;
  flex: none;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--tap-target);
  margin: 0;
  padding: var(--space-1) var(--space-3);
  border: 2px solid var(--color-border);
  border-radius: 999px;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--color-text);
  white-space: nowrap;
  text-decoration: none;
}

/* A partner's badge is a button: it opens the popup with its transfers. */
.node-partner {
  background: var(--color-surface);
  cursor: pointer;
}

.node-badge-active {
  border-color: var(--color-accent);
  background: var(--color-accent-tint);
}

.node-partner-seen {
  border-color: var(--color-seen);
}

.node-partner-unseen {
  border-color: var(--color-unseen);
}

/*
 * The transfer icons of a partner: an arrow up for data arriving from it
 * (upstream) and an arrow down for data going to it (downstream), an
 * outline each with a fill on top. While a transfer runs, and for the
 * second after it completed, the fill is clipped from the bottom and the
 * clip rises in a loop, so the arrow fills up again and again; the loop
 * starts at the phase `--transfer-phase` gives (negative, the elapsed
 * part of the shared loop), which keeps an icon that was rebuilt in
 * step with the one it replaced. A failed transfer flashes the icon red
 * once. With reduced motion the fill simply shows, in the active colour,
 * for the same time.
 */
.transfer-icons {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  margin-left: var(--space-1);
}

.transfer-icon {
  display: inline-flex;
  width: 1rem;
  height: 1rem;
  color: var(--color-text-muted);
}

.transfer-icon svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.transfer-icon-outline {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linejoin: round;
}

.transfer-icon-fill {
  fill: currentColor;
  clip-path: inset(100% 0 0 0);
}

.transfer-icon.is-receiving,
.transfer-icon.is-sending,
.transfer-icon.is-trailing {
  color: var(--color-seen);
}

.transfer-icon.is-receiving .transfer-icon-fill,
.transfer-icon.is-sending .transfer-icon-fill,
.transfer-icon.is-trailing .transfer-icon-fill {
  animation: transfer-fill 700ms linear infinite;
  animation-delay: var(--transfer-phase, 0ms);
}

.transfer-icon.is-failed {
  color: var(--color-unseen);
  animation: transfer-flash 1000ms ease-out 1;
}

.transfer-icon.is-failed .transfer-icon-fill {
  clip-path: none;
}

@keyframes transfer-fill {
  from {
    clip-path: inset(100% 0 0 0);
  }

  to {
    clip-path: inset(0 0 0 0);
  }
}

@keyframes transfer-flash {
  0%,
  100% {
    opacity: 1;
  }

  50% {
    opacity: 0.3;
  }
}

@media (prefers-reduced-motion: reduce) {
  .transfer-icon.is-receiving .transfer-icon-fill,
  .transfer-icon.is-sending .transfer-icon-fill,
  .transfer-icon.is-trailing .transfer-icon-fill {
    animation: none;
    clip-path: none;
  }

  .transfer-icon.is-failed {
    animation: none;
  }
}

/*
 * The hub's connected clients, a small count next to the node's name in
 * the header and on the node cards; absent on every node that is not the
 * hub.
 */
.node-clients {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 var(--space-1);
  border-radius: 999px;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  color: var(--color-accent-contrast);
  background: var(--color-accent);
}

/*
 * The browser's own probe is the secondary signal, so it is a small glyph
 * inside the link rather than the outline: a check when this browser
 * reached the node, a cross when it did not, an ellipsis while pending.
 */
.browser-probe {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  height: 1rem;
  border-radius: 50%;
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1;
  color: var(--color-accent-contrast);
  background: var(--color-text-muted);
}

.browser-probe::before {
  content: '…';
}

.browser-probe-ok {
  background: var(--color-seen);
}

.browser-probe-ok::before {
  content: '✓';
}

.browser-probe-failed {
  background: var(--color-unseen);
}

.browser-probe-failed::before {
  content: '✕';
}

.topology-dot {
  display: inline-block;
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
}

.topology-dot-seen {
  background: var(--color-seen);
}

.topology-dot-unseen {
  background: var(--color-unseen);
}

.probe-mark {
  display: inline-block;
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 0.125rem;
}

.probe-mark-ok {
  background: var(--color-seen);
}

.probe-mark-failed {
  background: var(--color-unseen);
}

.app-navigation {
  grid-area: navigation;
  position: sticky;
  bottom: 0;
  z-index: 1;
  background: var(--color-surface);
  border-top: 1px solid var(--color-border);
}

.app-navigation ul {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
}

.app-navigation li {
  flex: 1 1 0;
  min-width: 0;
}

.app-navigation a {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-width: var(--tap-target);
  min-height: var(--bottom-navigation-height);
  padding: var(--space-2);
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--color-text-muted);
  text-decoration: none;
}

.app-navigation a[aria-current='page'] {
  color: var(--color-accent);
}

.app-main {
  grid-area: main;
  width: 100%;
  min-width: 0;
  max-width: var(--content-max-width);
  padding: var(--space-4);
}

.view-title {
  margin: 0 0 var(--space-4);
  font-size: 1.5rem;
  line-height: 1.25;
}

.card-list {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

.card {
  padding: var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

a.card {
  display: block;
  min-height: var(--tap-target);
  color: inherit;
  text-decoration: none;
}

/* The badge is a round motif on a transparent background, so the surface
   of the card shows through in light and dark mode alike. The square
   aspect ratio holds the space before the bytes arrive. */
.species-image {
  display: block;
  width: 100%;
  max-width: 14rem;
  height: auto;
  aspect-ratio: 1;
  margin: 0 0 var(--space-3);
}

.species-name {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
}

.species-latin-name {
  margin: var(--space-1) 0 var(--space-3);
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.species-description {
  max-width: var(--text-measure);
  margin: 0 0 var(--space-4);
}

/* The two actions of a species card side by side, wrapping on a narrow
   card; the upload control is a label over a visually hidden file input,
   so the label carries the button's tap target and the input keeps the
   keyboard focus ring on it. */
.species-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
}

.species-upload {
  position: relative;
}

.species-upload-input:focus-visible + .button {
  outline: 3px solid var(--color-focus);
  outline-offset: 2px;
}

.species-upload-input:disabled + .button {
  opacity: 0.6;
  cursor: default;
}

.species-upload-message {
  margin: var(--space-3) 0 0;
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.species-upload-error {
  color: var(--color-error);
}

a.animal-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.animal-thumbnail {
  flex: none;
  display: block;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--color-background);
}

.animal-card-text {
  min-width: 0;
  flex: 1 1 auto;
}

.animal-name {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
}

.animal-meta {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin: var(--space-1) 0 var(--space-3);
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.animal-price {
  margin: 0;
  font-weight: 600;
}

.breeder-farm-name {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
}

.breeder-meta {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin: var(--space-1) 0 0;
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.back-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  margin-bottom: var(--space-2);
  color: var(--color-text-muted);
  text-decoration: none;
}

.animal-overview {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-4);
  margin: 0 0 var(--space-6);
}

.animal-species-image {
  flex: none;
  display: block;
  width: 6rem;
  height: 6rem;
}

.animal-overview .animal-facts {
  flex: 1 1 12rem;
  margin: 0;
}

.animal-facts,
.facts {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: var(--space-4);
  row-gap: var(--space-2);
  max-width: var(--text-measure);
  margin: 0 0 var(--space-6);
  font-size: 0.9375rem;
}

.animal-facts dt,
.facts dt {
  margin: 0;
  color: var(--color-text-muted);
}

.animal-facts dd,
.facts dd {
  min-width: 0;
  margin: 0;
}

.network-section {
  margin: 0 0 var(--space-6);
}

.network-section-title {
  margin: 0 0 var(--space-3);
  font-size: 1.125rem;
  line-height: 1.3;
}

.network-section .facts {
  margin-bottom: 0;
}

.role-badge {
  display: inline-block;
  padding: 0 var(--space-2);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--color-text-muted);
  background: var(--color-background);
  border: 1px solid var(--color-border);
}

.role-hub {
  color: var(--color-accent-contrast);
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.role-client {
  color: var(--color-text);
  background: var(--color-accent-tint);
  border-color: var(--color-accent-tint);
}

.identity-badge {
  display: inline-block;
  padding: 0 var(--space-2);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5;
  color: var(--color-text-muted);
  background: var(--color-background);
  border: 1px dashed var(--color-border);
}

.identity-persistent {
  color: var(--color-text);
  border-style: solid;
}

.node-id {
  font-size: 0.8125rem;
  overflow-wrap: anywhere;
}

.node-card-name {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.3;
}

.node-card-self {
  font-weight: 400;
  color: var(--color-text-muted);
}

.node-card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin: var(--space-1) 0 var(--space-3);
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.node-signals {
  display: grid;
  gap: var(--space-1);
  margin: 0 0 var(--space-3);
  padding: 0;
  list-style: none;
  font-size: 0.9375rem;
}

.node-signal {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.node-card-seen {
  margin: 0;
  font-size: 0.875rem;
  color: var(--color-text-muted);
}

.peer-card .facts {
  margin: var(--space-3) 0 0;
  font-size: 0.875rem;
}

.sync .facts {
  margin-bottom: var(--space-4);
}

.transfer-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.transfer {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--color-surface);
  font-size: 0.875rem;
}

.transfer-incoming {
  border-left-color: var(--color-paid);
}

.transfer-outgoing {
  border-left-color: var(--color-accent);
}

.transfer p {
  margin: 0;
}

.transfer-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

.transfer-direction {
  font-weight: 600;
}

.transfer-status {
  margin-left: auto;
  padding: 0 var(--space-2);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5rem;
  text-transform: capitalize;
}

.transfer-status-completed {
  color: var(--color-paid);
  background: var(--color-paid-tint);
}

.transfer-status-pending {
  color: var(--color-open);
  background: var(--color-open-tint);
}

.transfer-status-failed {
  color: var(--color-error);
  background: var(--color-error-tint);
}

.transfer-change-set {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  overflow-wrap: anywhere;
}

.transfer-hash {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.transfer-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  color: var(--color-text-muted);
}

.transfer-error {
  color: var(--color-error);
  overflow-wrap: anywhere;
}

/*
 * The transfer popup of a partner node: a modal dialog that fills the
 * screen on a phone and sits centred with a bounded height on a wide
 * screen (see the wide-screen rules at the end), with the header (name,
 * link, close button) staying put while the transfer list scrolls.
 */
.transfer-dialog {
  display: none;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  color: var(--color-text);
  background: var(--color-background);
}

.transfer-dialog[open] {
  display: flex;
  flex-direction: column;
}

.transfer-dialog::backdrop {
  background: rgb(0 0 0 / 45%);
}

.transfer-dialog-header {
  display: flex;
  flex: none;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
}

.transfer-dialog-heading {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.transfer-dialog-title {
  margin: 0;
  font-size: 1.125rem;
  line-height: 1.3;
}

.transfer-dialog-link {
  font-size: 0.875rem;
  overflow-wrap: anywhere;
}

.transfer-dialog-close {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 1.25rem;
  color: var(--color-text);
  background: transparent;
  cursor: pointer;
}

.transfer-dialog-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: var(--space-3) var(--space-4) var(--space-6);
  overflow-y: auto;
}

.transfer-dialog-body .status {
  margin: 0 0 var(--space-3);
}

.transfer-rows {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.transfer-row {
  border: 1px solid var(--color-border);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--color-surface);
  font-size: 0.875rem;
}

.transfer-row-incoming {
  border-left-color: var(--color-paid);
}

.transfer-row-outgoing {
  border-left-color: var(--color-accent);
}

/*
 * The row itself is the button that expands it: the direction icon, the
 * tables, the short hash, the row count, the time, the duration and the
 * status wrap as the width allows, and the chevron at the end turns
 * when the payload is shown.
 */
.transfer-row-toggle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1) var(--space-3);
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  border: 0;
  border-radius: var(--radius);
  font: inherit;
  font-size: 0.875rem;
  color: inherit;
  text-align: left;
  background: transparent;
  cursor: pointer;
}

.transfer-row-toggle .transfer-icon {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
}

.transfer-row-tables {
  flex: 1 1 10rem;
  min-width: 0;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.transfer-row-hash {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.transfer-row-count,
.transfer-row-time,
.transfer-row-duration {
  color: var(--color-text-muted);
}

/* The blob a pull fetched with the rows stands out from the counts: it
   is the one thing in the row that took bytes, not rows. */
.transfer-row-blobs {
  color: var(--color-accent);
  font-weight: 600;
}

.transfer-row-count:empty,
.transfer-row-blobs:empty,
.transfer-row-duration:empty {
  display: none;
}

.transfer-row-chevron {
  flex: none;
  width: 0.625rem;
  height: 0.625rem;
  margin-left: auto;
  border-right: 2px solid var(--color-text-muted);
  border-bottom: 2px solid var(--color-text-muted);
  transform: rotate(45deg);
  transition: transform 150ms ease-out;
}

.transfer-row-toggle[aria-expanded='true'] .transfer-row-chevron {
  transform: rotate(-135deg);
}

.transfer-row-error {
  margin: 0;
  padding: 0 var(--space-3) var(--space-2);
  color: var(--color-error);
  overflow-wrap: anywhere;
}

.transfer-payload {
  padding: 0 var(--space-3) var(--space-3);
  border-top: 1px solid var(--color-border);
}

.transfer-payload .status {
  margin: var(--space-3) 0 0;
}

/*
 * The payload of a change set: the change set in one line, every data
 * row as a small section with its fields (next to the version it
 * supersedes, the changed fields on a tinted background, the unchanged
 * ones muted), every history row in one muted line. Below 768 px the
 * two versions of a field stack, each labelled; from 768 px they sit
 * side by side under a header row.
 */
.payload-change-set {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin: var(--space-3) 0 0;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.payload-change-set-id {
  font-weight: 600;
  color: var(--color-text);
}

.payload-hash {
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.payload-item {
  margin: var(--space-3) 0 0;
}

.payload-item-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: 0.9375rem;
}

.payload-note {
  margin: 0;
  color: var(--color-text-muted);
}

.payload-history {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2);
  margin: var(--space-2) 0 0;
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

.payload-history-table {
  font-weight: 600;
}

.field-list {
  display: grid;
  grid-template-columns: minmax(6rem, auto) minmax(0, 1fr);
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin: 0;
}

.field-list dt,
.field-list dd {
  min-width: 0;
  margin: 0;
}

.field-name {
  font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
  font-size: 0.8125rem;
  color: var(--color-text-muted);
}

.field-value {
  min-width: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.field-compare {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
}

.field-compare-header {
  display: none;
}

.field-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-1);
  padding: var(--space-2);
  border-radius: calc(var(--radius) / 2);
}

.field-changed {
  background: var(--color-open-tint);
}

.field-changed .field-after {
  font-weight: 600;
}

.field-unchanged {
  color: var(--color-text-muted);
}

.payload-item-replaces {
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--color-text-muted);
}

.field-before::before,
.field-after::before {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.field-before::before {
  content: 'Before';
}

.field-after::before {
  content: 'After';
}

.field-changed-region {
  border-radius: 0.125rem;
  background: var(--color-accent-tint);
  box-shadow: 0 0 0 2px var(--color-accent-tint);
}

.field-show-all {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-target);
  margin-left: var(--space-2);
  padding: 0 var(--space-2);
  border: 0;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-accent);
  background: transparent;
  cursor: pointer;
}

.node-card-name .transfer-icons {
  vertical-align: middle;
}

.node-card-transfers {
  margin-top: var(--space-3);
}

/*
 * The background story is the reading-comfort surface of this view: at
 * least 17px so it stays comfortable on a phone, a relaxed line height, and
 * a measure kept to roughly 65 to 70 characters on wide screens so a line
 * of text never grows too long to track by eye.
 */
.animal-story {
  max-width: var(--story-measure);
}

.animal-story p {
  margin: 0 0 var(--space-5);
  font-size: 1.0625rem;
  line-height: 1.6;
}

.animal-story p:last-child {
  margin-bottom: 0;
}

/*
 * The search field sits above the filters of the animals view: full width,
 * the same 44px control as every other input, with the count line below the
 * filters telling how many of the matching animals are on the page and the
 * "Load more" button after the cards fetching the next page.
 */
.search-field {
  margin: 0 0 var(--space-4);
}

.search-input {
  max-width: var(--text-measure);
}

.animal-count {
  margin: 0 0 var(--space-3);
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.load-more {
  width: 100%;
  max-width: 20rem;
  margin: var(--space-4) 0 0;
}

/*
 * The animals view combines up to three filter groups (species, traits and,
 * while a breeder is selected, a compact breeder summary) that must all fit
 * on a 360px phone without the page scrolling horizontally: each is one
 * labelled row whose own chips scroll sideways within the row
 * (`overflow-x: auto` below) instead of the page growing wider than the
 * viewport. The breeder group stays a two-chip summary rather than a full
 * chip row (see `breederSummary` in `animals-list.js`) precisely so a third
 * row never has more content than the phone can show at once.
 */
.animal-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.filter-group {
  min-width: 0;
}

.filter-group-label {
  margin: 0 0 var(--space-1);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--color-text-muted);
}

.chip-row {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-2);
  padding-bottom: var(--space-1);
  overflow-x: auto;
}

.animal-traits {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0 0 var(--space-6);
  padding: 0;
  list-style: none;
}

.chip {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--color-text);
  white-space: nowrap;
  text-decoration: none;
}

.chip[aria-current='page'] {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

.status {
  max-width: var(--text-measure);
  margin: 0;
  color: var(--color-text-muted);
}

.status-error {
  padding: var(--space-4);
  border: 1px solid var(--color-error);
  border-radius: var(--radius);
  color: var(--color-text);
}

.status-headline {
  margin: 0;
  font-weight: 600;
  color: var(--color-error);
}

.status-detail {
  margin: var(--space-2) 0 var(--space-4);
  color: var(--color-text-muted);
}

.not-found p {
  max-width: var(--text-measure);
  margin: 0 0 var(--space-4);
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-5);
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  color: var(--color-accent-contrast);
  background: var(--color-accent);
  text-decoration: none;
  cursor: pointer;
}

.button:disabled {
  opacity: 0.6;
  cursor: default;
}

.button-secondary {
  border: 1px solid var(--color-accent);
  color: var(--color-accent);
  background: var(--color-surface);
}

/*
 * The invoices view puts its one action, "New invoice", next to the title
 * so it is reachable without scrolling past the list; on a phone the two
 * wrap onto separate lines rather than squeezing the title.
 */
.view-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-4);
}

.view-header .view-title {
  margin: 0;
}

.section-title {
  margin: var(--space-6) 0 var(--space-3);
  font-size: 1.125rem;
  line-height: 1.3;
}

.status-badge {
  display: inline-block;
  padding: 0 var(--space-2);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5rem;
  text-transform: capitalize;
  white-space: nowrap;
}

.status-badge-open {
  color: var(--color-open);
  background: var(--color-open-tint);
}

.status-badge-paid {
  color: var(--color-paid);
  background: var(--color-paid-tint);
}

.status-badge-cancelled {
  color: var(--color-cancelled);
  background: var(--color-cancelled-tint);
}

.invoice-card-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.invoice-number {
  margin: 0;
  font-size: 1.25rem;
  line-height: 1.3;
}

.invoice-meta {
  display: flex;
  flex-wrap: wrap;
  column-gap: var(--space-3);
  row-gap: var(--space-1);
  margin: var(--space-1) 0 var(--space-3);
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.invoice-total {
  margin: 0;
  font-weight: 600;
}

/*
 * Invoice lines are shared by the form (with a quantity stepper) and the
 * detail view (with the quantity as text): name and price on the left,
 * the line total on the right, the stepper between them when present.
 */
.invoice-line-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.invoice-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.invoice-line-text {
  display: flex;
  flex: 1 1 10rem;
  flex-direction: column;
  min-width: 0;
}

.invoice-line-name {
  font-weight: 600;
}

.invoice-line-price {
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.invoice-line-total {
  margin-left: auto;
  font-weight: 600;
  white-space: nowrap;
}

.invoice-total-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  border-top: 2px solid var(--color-border);
  font-size: 1.0625rem;
}

.invoice-total-amount {
  font-size: 1.25rem;
}

.invoice-change-set {
  margin: var(--space-6) 0 0;
  font-size: 0.8125rem;
  color: var(--color-text-muted);
  overflow-wrap: anywhere;
}

/* The invoice form: every control is at least a 44 px target. */
.invoice-form {
  display: flex;
  flex-direction: column;
  max-width: var(--text-measure);
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin: 0 0 var(--space-4);
}

.form-label {
  font-size: 0.9375rem;
  font-weight: 600;
}

.select,
.text-input {
  width: 100%;
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font: inherit;
  color: var(--color-text);
  background: var(--color-surface);
}

.animal-picker-list {
  display: grid;
  gap: var(--space-2);
  max-height: 22rem;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.animal-picker-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}

.animal-picker-text {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-width: 0;
}

.animal-picker-name {
  font-weight: 600;
}

.animal-picker-meta {
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.animal-picker-empty {
  padding: var(--space-3) 0;
  color: var(--color-text-muted);
}

.animal-picker-hint {
  margin: 0 0 var(--space-2);
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.animal-picker-hint:empty {
  display: none;
}

.stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
}

.stepper-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-target);
  min-height: var(--tap-target);
  border: 0;
  border-radius: 999px;
  font: inherit;
  font-size: 1.25rem;
  font-weight: 600;
  color: var(--color-accent);
  background: transparent;
  cursor: pointer;
}

.stepper-value {
  min-width: 2rem;
  font-weight: 600;
  text-align: center;
}

.form-error {
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-error);
  border-radius: var(--radius);
  color: var(--color-text);
  background: var(--color-error-tint);
}

.invoice-form .button[type='submit'] {
  align-self: flex-start;
  margin-top: var(--space-4);
}

/*
 * The animal edit form: the same fields and controls as the invoice form,
 * plus a story field that grows with its text, trait toggle chips and a
 * per-field validation message that appears below its control.
 */
.animal-form {
  display: flex;
  flex-direction: column;
  max-width: var(--text-measure);
}

.textarea {
  min-height: 6rem;
  resize: vertical;
  line-height: 1.6;
  overflow: hidden;
}

.form-fieldset {
  margin: 0 0 var(--space-4);
  padding: 0;
  border: 0;
}

.form-fieldset legend {
  padding: 0;
  margin-bottom: var(--space-1);
}

.trait-toggles {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.chip[aria-pressed='true'] {
  border-color: var(--color-accent);
  background: var(--color-accent);
  color: var(--color-accent-contrast);
}

button.chip {
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
}

.field-error {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--color-error);
}

[aria-invalid='true'] {
  border-color: var(--color-error);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/*
 * The version list on the animal detail: one tappable row per version,
 * newest first, the version on screen marked, the tip badged "current".
 */
.version-notice {
  max-width: var(--text-measure);
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-open);
  border-radius: var(--radius);
  color: var(--color-text);
  background: var(--color-open-tint);
}

.animal-versions {
  margin-top: var(--space-6);
}

.version-list {
  display: grid;
  gap: var(--space-2);
  max-width: var(--text-measure);
  margin: 0;
  padding: 0;
  list-style: none;
}

.version-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap-target);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  color: inherit;
  text-decoration: none;
}

.version-link[aria-current='page'] {
  border-color: var(--color-accent);
}

.version-text {
  display: flex;
  flex: 1 1 0;
  flex-direction: column;
  min-width: 0;
}

.version-time {
  font-weight: 600;
}

.version-summary {
  font-size: 0.9375rem;
  color: var(--color-text-muted);
}

.version-badge {
  padding: 0 var(--space-2);
  border-radius: 999px;
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.5rem;
  color: var(--color-paid);
  background: var(--color-paid-tint);
  white-space: nowrap;
}

@media (hover: hover) {
  .app-navigation a:hover {
    color: var(--color-accent);
  }

  .button:hover {
    filter: brightness(1.1);
  }

  .stepper-button:hover {
    background: var(--color-accent-tint);
  }

  .chip:not([aria-current='page']):not([aria-pressed='true']):hover {
    border-color: var(--color-accent);
  }

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

  .node-partner:hover,
  .transfer-dialog-close:hover,
  .transfer-row-toggle:hover {
    background: var(--color-accent-tint);
  }

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

  .back-link:hover {
    color: var(--color-accent);
  }
}

@media (min-width: 768px) {
  body {
    grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    grid-template-rows: auto 1fr;
    grid-template-areas:
      'header header'
      'navigation main';
  }

  .app-header {
    padding: 0 var(--space-6);
  }

  .app-navigation {
    top: var(--header-height);
    bottom: auto;
    align-self: start;
    height: calc(100vh - var(--header-height));
    height: calc(100dvh - var(--header-height));
    padding: var(--space-4) var(--space-3);
    border-top: 0;
    border-right: 1px solid var(--color-border);
  }

  .app-navigation ul {
    flex-direction: column;
    gap: var(--space-1);
  }

  .app-navigation a {
    flex-direction: row;
    justify-content: flex-start;
    gap: var(--space-3);
    min-height: var(--tap-target);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius);
    font-size: 1rem;
  }

  .app-navigation a[aria-current='page'] {
    background: var(--color-accent-tint);
  }

  .app-main {
    padding: var(--space-6) var(--space-8);
  }

  .view-title {
    font-size: 1.75rem;
  }

  .card-list {
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: var(--space-5);
  }

  .card {
    padding: var(--space-5);
  }

  .animal-species-image {
    width: 8rem;
    height: 8rem;
  }

  .transfer-dialog {
    width: min(44rem, calc(100vw - 2 * var(--space-8)));
    height: auto;
    max-height: min(85vh, 60rem);
    margin: auto;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
  }

  .transfer-dialog-header,
  .transfer-dialog-body {
    padding-right: var(--space-6);
    padding-left: var(--space-6);
  }

  .field-compare-header,
  .field-row {
    display: grid;
    grid-template-columns: minmax(6rem, 10rem) minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--space-3);
  }

  .field-compare-header {
    padding: 0 var(--space-2);
  }

  .field-compare-heading {
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }

  .field-before::before,
  .field-after::before {
    display: none;
  }

  .field-same {
    grid-column: 2 / -1;
  }
}
