/* Sindisa — foster animal management.
   Native CSS only: custom properties, grid, flexbox, nesting, :has().
   No framework, no utility classes. */

/* The palette lives in tokens.css, loaded alongside this file. */

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

body {
  margin: 0;
  font-family: var(--sans);
  font-size: var(--text-base);
  line-height: 1.55;
  color: var(--ink);
  background: var(--paper);
}

/* #container / .main / #content are the Django admin's wrappers; the site
   uses the same ones so a single stylesheet dresses both. */
#content {
  max-width: var(--measure);
  margin-inline: auto;
  padding: 1.5rem 1rem 4rem;
}

/* The navbar is fixed, so it takes no space of its own: the whole container
   is moved clear of it. Held here rather than on #content because the admin
   puts its breadcrumbs outside the content column, and they have to clear the
   menu too. It is only on the page when someone is signed in, which is why
   this is conditional. */
body:has(nav.navbar) #container { padding-inline-start: var(--sidebar-w); }

@media (max-width: 47.99rem) {
  body:has(nav.navbar) #container {
    padding-inline-start: 0;
    padding-block-start: var(--topbar-h);
  }
}

/* Ratios of the base, like the steps below it: the headings keep the
   proportions they had whatever size the body text is set to, rather than
   the hierarchy flattening every time the base moves. */
h1, h2, h3 { line-height: 1.2; text-wrap: balance; margin: 0 0 0.5rem; }
h1 { font-size: calc(var(--text-base) * 1.6); }
h2 { font-size: calc(var(--text-base) * 1.2); margin-top: 2rem; }
h3 { font-size: var(--text-base); }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
a:hover { text-decoration-thickness: 2px; }

/* ---------- navbar ----------

   One element in two shapes: a fixed sidebar from 768px up, and a fixed
   top bar below that whose menu slides in from the left. The open and
   close state is held by a checkbox rather than by script, so the menu
   works with JavaScript turned off. */

nav.navbar {
  position: fixed;
  z-index: 20;
  background: var(--nav-bg);

  & .nav-toggle {
    /* Transparent, and sitting over the hamburger: tapping the icon is
       really tapping this. Still focusable, so the menu opens from the
       keyboard too. */
    opacity: 0;
    margin: 0;
    -webkit-tap-highlight-color: transparent;
  }

  & .menu-toggle {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    height: var(--topbar-h);
    padding-inline: 1.25rem;

    /* The Kitten Clinic logo, with the wordmark left in the markup as the
       link's accessible name and pushed out of sight. */
    & a.brand {
      flex: 1;
      min-width: 0;
      height: calc(var(--topbar-h) - 0.75rem);
      background: url("../img/kc-logo.4b56e21f014a.png") left center / contain no-repeat;
      color: transparent;
      font-size: 0;
      text-indent: -9999px;
      overflow: hidden;
      transition: filter 0.2s;
    }

    & .menu {
      display: none;

      & .icon > svg { width: 30px; height: 30px; fill: var(--accent-strong); }
    }
  }

  & ul.nav-links {
    list-style: none;
    margin: 0;
    padding: 0.4rem 0 0.6rem;
    border-top: 1px solid var(--line-soft);

    & li {
      display: block;
      margin-inline: 0.6rem;
      border-radius: var(--radius);
      transition: background 0.1s;

      &:hover { background: var(--nav-hover); }
    }

    & a,
    & button {
      display: block;
      width: 100%;
      padding: 0.45rem 0.6rem;
      border: 0;
      background: none;
      font: inherit;
      font-size: var(--text-ui);
      text-align: start;
      text-decoration: none;
      color: color-mix(in srgb, var(--nav-fg) 70%, transparent);

      &:hover { color: var(--nav-fg); text-decoration: none; cursor: pointer; }
    }

    /* The page open: in full colour on the hover tint, nothing louder. */
    & li:has(> a[aria-current="page"]) { background: var(--nav-hover); }
    & a[aria-current="page"] { color: var(--nav-fg); font-weight: 550; }

    & form { margin: 0; }

    /* The search box. Its submit button is an icon sitting inside the
       field, so it opts out of the block styling the other items share. */
    & li.search {
      margin-block: 0.5rem 0.7rem;
      border-radius: 0;

      &:hover { background: none; }

      & form { position: relative; }

      & input[type="search"] {
        padding-inline-end: 1.9rem;
        font-size: var(--text-small);
      }

      & button {
        position: absolute;
        inset-block-start: 50%;
        inset-inline-end: 0.2rem;
        translate: 0 -50%;
        width: auto;
        padding: 0.15rem 0.3rem;
        line-height: 0;
        opacity: 0.6;

        & svg { fill: var(--nav-fg); }
        &:hover { opacity: 1; background: none; }
      }
    }

    /* Who is signed in, and as what. Not a link, so it is not styled as one. */
    & li.identity {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: 0.4rem;
      margin-top: 0.6rem;
      padding: 0.5rem 0.6rem 0.2rem;
      border-top: 1px solid var(--line-soft);
      border-radius: 0;

      &:hover { background: none; }

      & .who {
        font-size: var(--text-ui);
        font-weight: 550;
        color: var(--nav-fg);
        overflow-wrap: anywhere;
      }
    }

    & li.theme-toggle {
      margin-top: 0.4rem;
      padding-top: 0.3rem;
      border-top: 1px solid var(--line-soft);
      border-radius: 0;

      &:hover { background: none; }

      & button {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        border-radius: var(--radius);

        &:hover { background: var(--nav-hover); }
        & .icon { flex: none; width: 1.1em; line-height: 0; }
      }
    }
  }
}

.role {
  text-transform: capitalize;
  font-size: var(--text-micro);
  letter-spacing: 0.03em;
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-pill);
  background: var(--accent-soft);
  color: var(--accent);
}

/* Show the current setting on the theme button. */
.theme-icon-auto, .theme-text-auto { display: inline-flex; }
.theme-icon-sun, .theme-text-light,
.theme-icon-moon, .theme-text-dark { display: none; }

:root[data-theme="light"] {
  & .theme-icon-auto, & .theme-text-auto { display: none; }
  & .theme-icon-sun, & .theme-text-light { display: inline-flex; }
}
:root[data-theme="dark"] {
  & .theme-icon-auto, & .theme-text-auto { display: none; }
  & .theme-icon-moon, & .theme-text-dark { display: inline-flex; }
}

/* ---------- navbar: sidebar (768px and up) ---------- */

@media (min-width: 48rem) {
  nav.navbar {
    inset-block-start: 0;
    inset-inline-start: 0;
    width: var(--sidebar-w);
    height: 100dvh;
    overflow: hidden auto;
    border-inline-end: 2px solid var(--nav-border);

    & .nav-toggle { display: none; }

    /* No hamburger to sit beside, so the logo gets the sidebar's full
       width and its own proportions rather than a top bar's height. */
    & .menu-toggle {
      height: auto;
      padding-block: 0.9rem;

      & a.brand { height: auto; aspect-ratio: 2560 / 1160; }
    }

    & ul.nav-links a { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  }
}

/* ---------- navbar: a sidebar that can be put away ----------

   Only while the page is narrower than the sidebar and a full measure of
   content side by side: --sidebar-w and --measure, which a media query
   cannot read. Wider than that the content is as wide as it gets, so
   putting the sidebar away would give it nothing, and there is no handle. */

@media (48rem <= width < calc(68rem + 220px)) {
  nav.navbar {
    /* The top bar's checkbox, here a round handle on the sidebar's edge,
       half way down the page. Unchecked is the sidebar as it always was;
       checked puts it away and leaves the handle at the page's edge to
       bring it back. */
    & .nav-toggle {
      position: fixed;
      inset-block: 0;
      inset-inline-start: calc(var(--sidebar-w) - 0.75rem);
      display: block;
      width: 1.5rem;
      height: 1.5rem;
      margin-block: auto;
      appearance: none;
      opacity: 1;
      border: 1px solid var(--nav-border);
      border-radius: 50%;
      /* A chevron in the border's teal, which no token can reach inside an
         image. It is the background, so it sits in the middle of the circle:
         towards the sidebar to put it away, and turned half round, circle and
         all, to point the other way and bring it back. */
      background: var(--nav-bg) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M14.75 6.5 9.25 12l5.5 5.5' fill='none' stroke='%231b7272' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 1rem no-repeat;
      cursor: pointer;

      &:checked { inset-inline-start: 0.5rem; rotate: 180deg; }
    }
    /* Put away by sliding off the page's edge, as the top bar's panel
       does. Moved by its offset rather than a transform, which would carry
       the handle off with it; and hidden once it is out of sight, so the
       keyboard does not wander into links nobody can see. */
    &:has(.nav-toggle:checked) { inset-inline-start: calc(-1 * var(--sidebar-w)); }
    & .nav-toggle:checked ~ * { visibility: hidden; }
  }

  /* Put away, the sidebar leaves only its handle, which the page clears. */
  body:has(.nav-toggle:checked) #container { padding-inline-start: 2rem; }
}

/* ---------- navbar: top bar (below 768px) ---------- */

@media (max-width: 47.99rem) {
  nav.navbar {
    inset-block-start: 0;
    inset-inline: 0;
    border-block-end: 2px solid var(--nav-border);

    & .nav-toggle {
      position: absolute;
      z-index: 50;
      inset-block-start: 0;
      inset-inline-start: 0;
      width: var(--topbar-h);
      height: var(--topbar-h);
      cursor: pointer;
    }

    & .menu-toggle {
      position: relative;
      padding-inline-start: var(--topbar-h);

      & .menu {
        display: flex;
        align-items: center;
        justify-content: center;
        position: absolute;
        inset-block-start: 0;
        inset-inline-start: 0;
        width: var(--topbar-h);
        height: var(--topbar-h);

        & .icon > svg { fill: var(--nav-fg); opacity: 0.65; }
        & .text { display: none; }
      }

      /* The close icon lies on top of the hamburger, clipped away until the
         menu opens, then wiped across as the panel slides in. */
      & .menu.close {
        background: var(--nav-bg);
        clip-path: polygon(0 0, -20px 0, -20px 100%, 0 100%);
      }
    }

    & ul.nav-links {
      position: fixed;
      inset-block-start: var(--topbar-h);
      inset-inline-start: 0;
      width: var(--sidebar-w);
      max-height: calc(100dvh - var(--topbar-h));
      overflow-y: auto;
      background: var(--nav-bg);
      border-inline-end: 2px solid var(--nav-border);
      border-block-end: 2px solid var(--nav-border);
      border-end-end-radius: var(--radius);
      transform: translateX(-100%);

      /* Above the checkbox, which becomes the whole screen once the menu is
         open: the links have to stay clickable through it. */
      z-index: 60;
    }

    & .nav-toggle:checked ~ ul.nav-links { transform: translateX(0); }

    /* An open menu grows its own checkbox to cover the page, so a tap anywhere
       outside the panel lands on the checkbox and slides the menu back in. It
       is the same control, so the hamburger still closes it too, and there is
       no script and no extra markup in it. */
    & .nav-toggle:checked {
      position: fixed;
      inset: 0;
      width: auto;
      height: auto;
    }
    & .nav-toggle:checked ~ .menu-toggle .menu.close {
      clip-path: polygon(0 0, var(--topbar-h) 0, var(--topbar-h) 100%, 0 100%);
    }
  }
}

/* Keep the transparent teal logo readable against dark backgrounds. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(nav.navbar a.brand, .login-logo) { filter: brightness(0) invert(1); }
}
:root[data-theme="dark"] :is(nav.navbar a.brand, .login-logo) { filter: brightness(0) invert(1); }

/* The sliding is deliberately not on until the document has settled.

   A phone browser lays the page out once at a default width before the
   viewport meta is applied, which matches the desktop rules and shows the menu
   as a sidebar; the re-layout at the real width then animated it closed, so
   every page appeared to open its menu and shut it again. Without JavaScript
   the class is never added and the menu simply opens and closes at once,
   which is the behaviour being enhanced. */
body.motion-ready nav.navbar {
  & ul.nav-links { transition: transform 0.5s cubic-bezier(0.77, 0.2, 0.05, 1); }
  & .menu.close { transition: clip-path 0.5s cubic-bezier(0.77, 0.2, 0.05, 1); }
}

/* The sidebar slides the same way, and its handle and the page move with it. */
@media (min-width: 48rem) {
  body.motion-ready {
    & nav.navbar { transition: inset-inline-start 0.5s cubic-bezier(0.77, 0.2, 0.05, 1); }
    & nav.navbar .nav-toggle {
      transition: 0.5s cubic-bezier(0.77, 0.2, 0.05, 1);
      transition-property: inset-inline-start, rotate;
    }
    & nav.navbar .nav-toggle:checked ~ * { transition: visibility 0s 0.5s; }
    &:has(nav.navbar) #container {
      transition: padding-inline-start 0.5s cubic-bezier(0.77, 0.2, 0.05, 1);
    }
  }
}

@media (prefers-reduced-motion: reduce) {
  body.motion-ready nav.navbar :is(ul.nav-links, .menu.close) { transition: none; }
  /* Spelled as they are above, so that these weigh the same and win by
     coming later. */
  body.motion-ready {
    & nav.navbar, & nav.navbar .nav-toggle, & nav.navbar .nav-toggle:checked ~ *,
    &:has(nav.navbar) #container { transition: none; }
  }
}

/* ---------- messages ---------- */

.messagelist {
  list-style: none;
  max-width: var(--measure);
  margin-inline: auto;
  padding: 0 1rem;
  display: grid;
  gap: 0.5rem;

  & li {
    padding: 0.6rem 0.85rem;
    border-radius: var(--radius);
    border-inline-start: 3px solid var(--accent);
    background: var(--accent-soft);
    font-size: var(--text-ui);
  }
  & li.warning { border-color: var(--warn); background: var(--warn-soft); }
  & li.error { border-color: var(--bad); background: var(--bad-soft); }
}

/* ---------- celebrations ---------- */

/* Good news over the page, in the brand's colours, with Dismiss beside it
   where there is room and under it where there is not. */
.celebration {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  max-width: var(--measure);
  margin: 0 auto 1rem;
  padding: 0.85rem 1rem;
  border: 2px solid var(--brand);
  border-radius: var(--radius);
  background: var(--brand-soft);
  color: var(--on-brand);

  &[hidden] { display: none; }
  & h2 { flex-basis: 100%; margin: 0; font-size: var(--text-base); }
  & ul { flex: 1 1 16rem; margin: 0; padding-inline-start: 1.25rem; }
  & form { margin: 0; }
}

/* Over everything, and never in the way of a click. */
.confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1000;
}

/* ---------- page furniture ---------- */

/* The heading and its actions, in the admin's own wrapper. */
.titles-and-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap);
  margin-bottom: 1.25rem;
}

.titles {
  min-width: 0;

  & > :last-child { margin-bottom: 0; }
}

.object-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

#content-main > h1 { margin-bottom: 1.25rem; }

/* The admin marks its two kinds of object tool; the site shows the same
   distinction with a leading glyph rather than a background image. */
.object-tools .addlink::before { content: "+ "; }

.submit-row.end { justify-content: end; }


/* One trail on one line. List items are blocks by default, which put each
   crumb on a line of its own. */
ol.breadcrumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.1rem 0.4rem;
  list-style: none;
  padding: 0;
  margin: 0 0 0.75rem;
  font-size: var(--text-small);
  color: var(--ink-soft);

  & li + li::before {
    content: ">";
    margin-inline-end: 0.4rem;
    color: var(--ink-faint);
  }
}

div.placement-history td.subtle {
  max-inline-size: 30rem;
}
.subtle { color: var(--ink-soft); font-size: var(--text-ui); }
.faint { color: var(--ink-faint); }
.empty {
  padding: 2rem 1rem;
  text-align: center;
  color: var(--ink-soft);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

/* ---------- cards and stats ---------- */

.module,
.stats > * {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.module {
  padding: 1rem;
  margin-bottom: 1rem;

  /* A heading carries the space that separates it from the section before
     it. Opening a card, there is no section before it — the card's own
     padding is the gap — so the margin would only push the title away from
     the top of its box. */
  & > :first-child { margin-top: 0; }
}

.invite-form {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
  border: 2px solid var(--bad);

  & > p {
    flex: 1 1 18rem;
    margin: 0;
  }

  & > button {
    flex: 0 0 auto;
    margin-inline-start: auto;
  }
}

/* Every way out of a card that edits something, on one line at its foot --
   wrapped rather than squeezed, since a confirmation step can offer three
   of them at once. */
.panel-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: end;
  gap: 0.75rem;
  margin-top: 1rem;
}

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 0.75rem;
  margin-bottom: 2rem;
  padding: 0;
  list-style: none;

  & > * { padding: 0.8rem 0.9rem; position: relative; }

  /* Only the label carries the href, but the whole card should answer a
     click — nobody aims for three words of text in a box this size. The
     pseudo-element stretches the hit area to the card around it, and the
     label itself is styled plainly rather than as a link: the card being
     clickable is what the pointer and the hover state already say, and a
     blue underlined word in one corner would say it a second time and only
     there. */
  & a.stretched-link::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  & dt, & .label,
  & dt a.stretched-link {
    font-size: var(--text-label);
    color: var(--ink-soft);
    margin: 0;
    text-decoration: none;
  }
  & dd, & .value {
    margin: 0.15rem 0 0;
    font-size: 1.55rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
  }
}

/* The tiles that head a queue, side by side: where the form can be filled in,
   and -- on a contract queue -- how many of its rows name an animal nobody
   could find. Each is only as wide as what it holds, so the row grows a tile
   at a time rather than stretching one across the page. */
.queue-tiles {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 0.75rem;
  margin-bottom: 1.25rem;

  /* A single tile has no grid to lay out, and the row below sets the spacing
     underneath both. */
  & > .stats {
    display: block;
    margin-bottom: 0;
  }
}

/* An address is read rather than compared, so it keeps the body's size instead
   of the display size a tile gives a number, and takes the width a line of
   text needs. */
.stats.application-link {
  flex: 1 1 24rem;

  & .address {
    font-size: var(--text-base);
    font-weight: 400;
    font-variant-numeric: normal;
    line-height: 1.4;
    overflow-wrap: anywhere;
  }
}

.modules {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.75rem;
  padding: 0;
  list-style: none;
}

/* Where there is room for several to a row, "In your care" is a handful of
   animals glanced over, not read: narrower tiles in smaller type, tighter
   inside, the two facts side by side, so more of them sit in a row. */
@media (min-width: 48rem) {
  .modules:not(.compact) {
    grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
    gap: 0.5rem;

    & > .module {
      padding: 0.5rem 0.65rem;
      margin-bottom: 0;
      font-size: var(--text-ui);
    }
    & .animal-summary {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 0.25rem 0.5rem;
      margin-top: 0.3rem;
    }
    & .animal-log-action { margin-top: 0.4rem; }
    & .animal-log-action .button { padding: 0.15rem 0.5rem; }
  }
}

/* "In my team's care": the same tiles as "In your care" above, smaller and
   plainer -- a team leader's overview of several fosters' animals at once,
   not one foster's own handful, so there is no Log today button here for a
   card that is not showing the animal's own carer where to put it. */
.modules.compact {
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0.5rem;

  & > .module {
    padding: 0.6rem 0.7rem;
    margin-bottom: 0;
  }
}

/* A foster's name over their animals, in a team leader's "My team". */
.team-foster {
  margin: 1.25rem 0 0.5rem;
  font-size: var(--text-base);

  & .subtle { font-weight: 400; }
}

/* The way out of a list a dashboard widget cut short. */
.more {
  margin: 0.8rem 0 0;
  font-size: var(--text-ui);
}

.address,
.origin { margin-bottom: 0; }

.no-capabilities,
.deceased-placement { margin: 0; }

/* A litter or a foster that opens onto its animals: the Litters and Fosters
lists, and a foster's page. */
:is(.litter-group, .foster-group) + :is(.litter-group, .foster-group) { margin-top: 0.6rem; }
:is(.litter-group, .foster-group) summary { cursor: pointer; }
:is(.litter-group, .foster-group)[open] summary { margin-bottom: 0.75rem; }
/* A foster's facts in the same columns on every row, so the list scans like
the table it replaced. Grid drops the native marker, so it is drawn again. */
.foster-group summary {
  display: grid;
  grid-template-columns: 1ch minmax(9rem, 2fr) minmax(6rem, 1.5fr) 3fr minmax(8rem, 1.5fr) 6rem 5rem 7rem;
  align-items: center;
  gap: 0.35rem 1rem;
}
/* Without staff-only fields: name, capabilities and care count. */
.foster-group.limited-summary summary {
  grid-template-columns: 1ch minmax(9rem, 2fr) minmax(0, 3fr) 7rem;
}
.foster-group summary::-webkit-details-marker { display: none; }
.foster-group summary::before { content: "▸"; }
.foster-group[open] summary::before { content: "▾"; }
/* Too narrow for columns: the name on its own line, the facts wrapping under
it. The marker sits in the padding, outside the flow, or it and the gap after
it push the name onto a line of its own. */
@media (max-width: 47.99rem) {
  .foster-group summary {
    display: flex;
    flex-wrap: wrap;
    position: relative;
    padding-inline-start: 2ch;
  }
  .foster-group summary::before { position: absolute; inset-block-start: 0; inset-inline-start: 0; }
  .foster-group summary > strong { flex-basis: 100%; }
}
/* A litter's answers, to copy into a new animal: each field's answers under
   its name, with the animals that gave one after it in a quieter voice. */
.litter-copy .copy-field {
  border: 0;
  padding: 0;
  margin: 0 0 0.75rem;
  background: none;

  & legend { padding: 0; font-size: var(--text-small); }

  & label {
    display: flex;
    gap: 0.4rem;
    align-items: baseline;
    font-size: var(--text-ui);
  }
}
.copy-from-litter { margin-top: 0.5rem; }
/* Where the page comes back to after copying, clear of the phone's top bar. */
#litter-copy { scroll-margin-block-start: 5rem; }

/* Each new animal's own answers side by side: the short ones four to a line,
   the rest two. */
.litter-animal {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
  gap: 0 1rem;

  & .form-row:has(textarea, input[type="file"], input[name$="-birth_override"]) {
    grid-column: span 2;
  }
  & > .errorlist { grid-column: 1 / -1; }

  /* Seldom needed -- a mother, among her kittens -- so greyed, and last. */
  & .form-row:has(input[name$="-birth_override"]) > label { color: var(--ink-faint); }
}
@media (max-width: 47.99rem) {
  .litter-animal .form-row:has(textarea, input[type="file"], input[name$="-birth_override"]) {
    grid-column: 1 / -1;
  }
}
.add-animal { margin-bottom: 1.25rem; }

/* Each medication given together: what it is, how much, and whose say-so,
   three to a line. On the daily log a second line follows: whether it was
   given, and by whom. */
.medication-row {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 1rem;

  & > .errorlist { grid-column: 1 / -1; }

  /* The tick box level with the box beside it, below that one's label. */
  & > .form-row:has(> input[type="checkbox"]) {
    padding-top: calc(var(--text-small) * 1.55 + 0.25rem);
  }
}
@media (max-width: 47.99rem) {
  .medication-row { grid-template-columns: minmax(0, 1fr); }
  .medication-row > .form-row:has(> input[type="checkbox"]) { padding-top: 0; }
}
.add-medication { margin-bottom: 1.25rem; }
.contact-module { margin-top: 0.75rem; }

/* Contact cards keep each value and its label together, like medication rows. */
.contact-list {
  border: 0;
  padding: 0;
  & > legend { padding: 0; margin-bottom: 0.75rem; }
}
.contact-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, min(14ch, 30%));
  gap: 0 0.4rem;
  align-items: end;

  & > .form-row { min-inline-size: 0; margin-bottom: 0; }
  & input:not([type="hidden"], [type="checkbox"]) { padding-inline: 0.4rem; max-inline-size: none; }
  & > .errorlist, & > .contact-remove { grid-column: 1 / -1; }
  & > .remove-entry { top: calc(var(--radius) * 0.29 - var(--size) / 2); }
}
/* Unfilled, as .kc-numbers' are: they change the list, not the page. */
.add-contact {
  background: transparent;
  color: var(--accent);
}
/* The button rule's display, and the tick box row's, would otherwise outrank
   [hidden]'s. */
.contact-list [hidden],
.contact-row > .form-row[hidden] { display: none; }
.phone-number {
  display: inline-block;
  white-space: nowrap;
}
.phone-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.5rem;
  min-height: 1.5rem;
  vertical-align: middle;
}
.description-module { margin-top: 1rem; }
.placement-note { margin: 0.75rem 0 0; }
.placement-history { margin-bottom: 1rem; }
.application-section + .application-section { margin-top: 0.6rem; }
.application-facts { margin-top: 0.75rem; }
.contract-text { white-space: pre-line; }
.dashboard-actions { margin-block-start: 1.5rem; }
.stale-warning { border-color: var(--warn); margin-bottom: 1.5rem; }
/* The animals in the box: pills in the brand teal, so they stand out from
   the foster names beside them. The edge is a shadow, so it adds no size. */
.stale-warning a.pill {
  background: var(--brand-soft);
  color: var(--on-brand);
  box-shadow: inset 0 0 0 1px var(--brand);
}
/* A foster a Core Team member leads, then their animals needing a weight:
   the person in plain bold text, the animals as pills, so the two never
   read as one list of names. A soft rule between
   fosters, and the names in a column of their own where there is room. */
.stale-by-foster {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;

  & > li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem;
    padding: 0.45rem 0;
    border-block-start: 1px solid var(--line-soft);
  }
  & > li:first-child { border-block-start: 0; }
}

/* Each worrying stool on the Core Team's dashboard, a line each. */
.worrying-stools {
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;

  & > li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.35rem 0.5rem;
    padding: 0.45rem 0;
    border-block-start: 1px solid var(--line-soft);
  }
  & > li:first-child { border-block-start: 0; }
}

.stale-foster {
  min-width: 10rem;
  margin-inline-end: 0.4rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;

  &:hover { text-decoration: underline; }
}
/* When the last weight on an "In your care" tile was taken, under it. */
.weighed-on {
  display: block;
  font-size: var(--text-label);
  color: var(--ink-soft);
}
.animal-log-action { margin: 0.75rem 0 0; }
/* An "In your care" tile whose animal is under eight weeks and not weighed
   in 18 hours: the warning box names none, so the tile has to stand out. */
.needs-weight {
  border-color: var(--warn);
  background: var(--warn-soft);
}
.needs-weight-note {
  display: block;
  margin-bottom: 0.4rem;
  font-size: var(--text-label);
  color: var(--warn-ink);
}
.foster-record-link { margin-top: 1.5rem; }

/* On a phone the tiles stack one to a row, so every line in one is a line to
   scroll past. The grid's gap alone between them; the button at the end of
   the name's row; each label beside its value rather than above it; and the
   warning under the facts. The paragraph holding the warning and the button
   steps aside (display: contents) so the two can be placed apart. */
@media (max-width: 47.99rem) {
  .modules:not(.compact) {
    gap: 0.5rem;

    & > .module {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      column-gap: 0.5rem;
      padding: 0.5rem 0.75rem;
      margin-bottom: 0;
    }
    & .animal-summary {
      order: 1;
      flex-basis: 100%;
      display: flex;
      flex-wrap: wrap;
      gap: 0 1rem;
      margin-top: 0.2rem;

      & > div {
        display: flex;
        align-items: baseline;
        gap: 0.5ch;
        white-space: nowrap;
      }
    }
    & .weighed-on {
      display: inline;
      margin-inline-start: 0.5ch;
    }
    & .animal-log-action { display: contents; }
    & .animal-log-action .button { margin-inline-start: auto; }
    & .needs-weight-note {
      order: 2;
      flex-basis: 100%;
      margin: 0.2rem 0 0;
    }
  }
}

/* ---------- a form's own prose ---------- */

/* An intro, a closing message or a section's blurb, written in the admin and
   rendered here. The tags are the short allowlist in core.richtext, so this
   is the whole of what can turn up. Scoped to .rich so that a heading inside
   somebody's paragraph does not take the size of the page's own h2. */
.rich {
  & > *:first-child { margin-top: 0; }
  & > *:last-child { margin-bottom: 0; }

  & p { margin: 0 0 0.7rem; }

  & h2, & h3 {
    font-size: var(--text-ui);
    font-weight: 600;
    margin: 1rem 0 0.4rem;
  }

  & ul, & ol { margin: 0 0 0.7rem; padding-inline-start: 1.4rem; }
  & li { margin-bottom: 0.2rem; }
  /* A list nested with the indent button sits inside its parent item, so it
     must not carry the gap that separates one list from the text after it. */
  & li > ul, & li > ol { margin-bottom: 0; }

  & blockquote {
    margin: 0 0 0.7rem;
    padding-inline-start: 0.8rem;
    border-inline-start: 3px solid var(--line);
    color: var(--ink-soft);
  }
}

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

.results { overflow-x: auto; }

/* A fieldset grows to its widest content, so a wide table would push the
   page sideways instead of scrolling in its own box. */
fieldset:has(> .results) { min-inline-size: 0; }

/* A card whose whole content is a table. The rows are the content, so they
   run to its edges rather than being inset by a second ring of padding: the
   cells carry spacing of their own, and a rule that stops short of the border
   it sits inside looks like a mistake. */
.module.results { padding: 0; }

/* ...and the card's own border is what closes the table off at the bottom.
   The last row's rule landing on top of it reads as a double line. Matched
   through tbody:last-child so a table with a totals row below it keeps the
   rule that separates the two. */
.module.results tbody:last-child tr:last-child > * { border-bottom: 0; }

/* A row of a table is mostly links, and underlining every one of them leaves
   the results looking ruled. The row highlights under the pointer, which is
   the cue an underline was carrying. */
.results a {
  text-decoration: none;
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-ui);

  & th, & td {
    text-align: start;
    padding: 0.55rem 0.7rem;
    border-bottom: 1px solid var(--line);
    vertical-align: top;
  }
  & th {
    font-size: var(--text-small);
    color: var(--ink-soft);
    font-weight: 600;
    white-space: nowrap;
  }
  /* Alternating rows, so a wide table reads across the columns you meant to
     read across rather than sliding down into the row below it. No separate
     hover: a row that already reads by its stripe does not need a second,
     louder colour on top of it once the pointer happens to be there. */
  & tbody tr:nth-child(even) { background: var(--accent-row); }
  & .num { text-align: end; font-variant-numeric: tabular-nums; }

  /* A totals row is read as the answer to the column above it, so it is set
     apart from the years rather than looking like one more of them. */
  & tfoot th, & tfoot td {
    border-top: 2px solid var(--line);
    border-bottom: 0;
    font-weight: 600;
    color: var(--ink);
  }
}

.kc { font-family: var(--mono); font-size: 0.88em; color: var(--ink-soft); }

/* A results table, on a phone: each row becomes a card of two lines --
   identity on top, the rest of the columns run together below it as a
   summary -- rather than a table too narrow to read across. Every cell is
   inline, so the two lines are running text, and the break between them is
   a newline drawn after the name: a cell made a block would break the line
   after itself as well as before. */
@media (max-width: 47.99rem) {
  table.stacked-results {
    & thead { display: none; }
    & tbody, & tr { display: block; }
    & tr {
      padding: 0.6rem 0.7rem;
      border-bottom: 1px solid var(--line);
    }
    & td {
      display: inline;
      border: 0;
      padding: 0;
      vertical-align: baseline;
    }
    & td.kc { margin-inline-end: 0.5rem; }
    & td:nth-child(2) { font-weight: 600; }
    & td:nth-child(2)::after { content: "\A"; white-space: pre; }
    & td:nth-child(n+3) {
      font-size: var(--text-small);
      color: var(--ink-soft);
    }
    & td:nth-child(n+4)::before { content: " · "; }
    /* .subtle sets a size of its own, larger than the line it sits in. */
    & td .subtle { font-size: inherit; }
  }

  /* A cell holding nothing but a status pill is set apart at the top right,
     wherever its column is, rather than left in the middle of the summary. */
  table.stacked-results:not(.single-identity) {
    & tr:has(> td > .pill:only-child) { position: relative; }
    /* Room for it on the first line only: a float one line tall, so the
       summary below runs the full width of the card. */
    & tr:has(> td > .pill:only-child)::before {
      content: "";
      float: right;
      inline-size: 7rem;
      block-size: 1lh;
    }
    & td:has(> .pill:only-child) {
      position: absolute;
      top: 0.6rem;
      right: 0.7rem;
    }
    & td:has(> .pill:only-child)::before { content: none; }
  }

  /* A table named by its first column alone -- a person or an application,
     rather than an animal's code and name together. */
  table.stacked-results.single-identity {
    & td:nth-child(1) { font-weight: 600; }
    & td:nth-child(1)::after { content: "\A"; white-space: pre; }
    & td:nth-child(2) {
      font-weight: normal;
      font-size: var(--text-small);
      color: var(--ink-soft);
    }
    & td:nth-child(2)::after { content: none; }
    & td:nth-child(3)::before { content: " · "; }
  }
}

/* A litter's batch page, on a phone: each animal laid out as a list item
   rather than a line of cells wrapping wherever the screen ends. The tick box
   and the photo run down the left of the name, with the status at the far
   end of it and the KC number and the foster beneath; its answers drop
   below, one to a line and labeled, rather than squeezing into columns. The cells are, in order: tick box, photo, KC, name, status,
   foster, then the answers. */
@media (max-width: 47.99rem) {
  table.batch-animals {
    & thead { display: none; }
    & tbody { display: block; }
    & tr {
      display: grid;
      grid-template-columns: auto auto auto 1fr auto;
      grid-template-areas:
        "tick photo name name status"
        "tick photo kc foster foster";
      align-items: center;
      gap: 0.2rem 0.6rem;
      padding: 0.6rem 0.7rem;
      border-bottom: 1px solid var(--line);
    }
    & td { border: 0; padding: 0; }
    /* Every one of these named, not just some: a cell given only its line is
       put in the first free column of it, ahead of those given nothing. */
    & td:nth-child(1) { grid-area: tick; }
    & td:nth-child(2) { grid-area: photo; }
    /* Smaller than elsewhere: a phone has no room to spare beside it. */
    & .batch-photo img, & .batch-photo:empty::before { inline-size: 4rem; }
    & td:nth-child(3) { grid-area: kc; }
    & td:nth-child(4) { grid-area: name; font-weight: 600; }
    & td:nth-child(5) { grid-area: status; }
    & td:nth-child(6) { grid-area: foster; color: var(--ink-soft); }
    & td:nth-child(6)::before { content: "· "; }
    & td:nth-child(3), & td:nth-child(6) { font-size: var(--text-small); }
    /* Without its column heading, the dash says nothing. */
    & .not-asked { display: none; }
    & tr:has(+ .row-errors) { border-bottom: 0; }
    & .row-errors { display: block; padding-block-start: 0; }
    & td[data-label] {
      grid-column: 1 / -1;
      display: flex;
      align-items: center;
      gap: 0.75rem;
      padding-block-start: 0.4rem;
    }
    /* Set against its answer rather than the left edge, which is a long way
       from the box it names. */
    & td[data-label]::before {
      content: attr(data-label);
      flex: 0 0 8rem;
      text-align: end;
      font-size: var(--text-small);
      font-weight: 600;
      color: var(--ink-soft);
    }
    & td[data-label] > :is(select, input:not([type="checkbox"]), textarea) {
      flex: 1;
      min-width: 0;
    }
    /* The last weight, and the difference from it, on a line of their own
       under the box: beside it, they leave the box too narrow. */
    & td[data-label]:has(> .help) { flex-wrap: wrap; row-gap: 0.2rem; }
    & td[data-label] > .help {
      flex-basis: 100%;
      padding-inline-start: 8.75rem;
      font-size: var(--text-label);
      color: var(--ink-soft);
      &:empty { display: none; }
    }
  }
}

/* A litter's batch page, wider than a phone, asking four things or more of
   each animal (a tenth cell, after the six saying who it is): each animal a
   band, rather than a table wider than the page. Who the animal is runs down
   the left -- tick, photo, then the name over the KC number, status and
   foster -- and the answers sit on a grid of equal columns beside it, each
   labeled above its box. Every animal is asked the same things in the same
   order, so each answer falls in the same column on every band. Fewer
   answers keep the table, which they fit without the band's room. */
@media (min-width: 48rem) {
  table.batch-animals:has(td:nth-child(10)) {
    & thead { display: none; }
    & tbody { display: block; }
    & tr {
      display: grid;
      grid-template-columns: 1.5rem 4.5rem 9rem repeat(auto-fill, minmax(9rem, 1fr));
      grid-template-rows: auto auto auto 1fr;
      align-items: start;
      column-gap: 1rem;
      padding: 0.75rem 0.7rem 0.15rem;
      border-bottom: 1px solid var(--line);
    }
    & td { border: 0; padding: 0; }
    /* The left-hand cells span far more lines than a band has, so no answer
       is placed under them; the lines left empty take no room, as long as
       there is no gap between lines -- the answers' margin stands in. */
    & td:nth-child(1) { grid-column: 1; grid-row: 1 / span 40; }
    /* The photo reaches a little into the gaps either side of its column,
       so it is larger without moving the name. */
    & td:nth-child(2) { grid-column: 2; grid-row: 1 / span 40; margin-inline: -0.25rem; }
    /* The name, KC number, status and foster each take a line of their own,
       just as tall as they are, and each answer the four: only the last,
       1fr, stretches to fit an answer taller than the four, so they are
       spaced the same whatever is beside them. */
    & td:is(:nth-child(3), :nth-child(4), :nth-child(5), :nth-child(6)) {
      grid-column: 3;
      padding-block-end: 0.2rem;
    }
    & td:nth-child(4) { grid-row: 1; font-weight: 600; }
    & td:nth-child(3) { grid-row: 2; font-size: var(--text-small); }
    & td:nth-child(5) { grid-row: 3; }
    & td:nth-child(6) { grid-row: 4 / span 37; font-size: var(--text-small); color: var(--ink-soft); }
    & td[data-label], & td.not-asked {
      grid-row: span 4;
      display: flex;
      flex-direction: column;
      gap: 0.2rem;
      margin-block-end: 0.6rem;
    }
    & td:has(textarea) { grid-column: span 2; }
    /* Where this animal is not asked: the room kept, so the next answer
       stays in its column. */
    & td.not-asked { visibility: hidden; }
    & td[data-label]::before {
      content: attr(data-label);
      font-size: var(--text-small);
      font-weight: 600;
      color: var(--ink-soft);
    }
    & td[data-label] > :is(select, input:not([type="checkbox"]), textarea) {
      width: 100%;
      min-width: 0;
    }
    & td[data-label] > input[type="checkbox"] { align-self: start; }
    & tr:has(+ .row-errors) { border-bottom: 0; }
    & .row-errors { display: block; padding: 0 0.7rem 0.5rem; }
  }
}

/* ---------- status pills ---------- */

.pill {
  display: inline-block;
  font-size: var(--text-label);
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--line);
  color: var(--ink-soft);
  white-space: nowrap;
}
.pill.good { background: var(--accent-soft); color: var(--accent); }
.pill.warn { background: var(--warn); color: var(--on-warn); }
.pill.bad { background: var(--bad-soft); color: var(--bad); }
.pill.reserved { background: var(--reserved-soft); color: var(--reserved); }

/* A pill that filters the list when clicked. Underlining a pill just muddies
   its shape, so the hover cue is the background instead. */
a.pill {
  text-decoration: none;
}
a.pill:hover {
  filter: brightness(0.94);
}

/* A row of pills, e.g. what a foster is willing to take on. */
ul.tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

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

form { margin-block: 0; }

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem 1.1rem 1.2rem;
  margin: 0 0 1.25rem;
  background: var(--card);

  & legend {
    padding-inline: 0.4rem;
    font-weight: 600;
    font-size: var(--text-ui);
  }
}

/* The star beside a required question's label, and the line at the top of the
   form saying what it means. */
.required {
  color: var(--bad);
  margin-inline-start: 0.15rem;
  font-weight: 600;
}

.required-note {
  margin-bottom: 1rem;
}

/* Not scoped to .form-row: a filter toolbar, a bare non-field-error block
   above a form, and .form-row's own fields all carry this class, and all
   read the same way -- small, in the same red as everything else that means
   "wrong". */
.errorlist {
  margin: 0.3rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-small);
  color: var(--bad);
}

/* Every error on the page, at its top: set apart in red, one to a line, each
   led by what is wrong as a link to it. */
.error-summary {
  margin: 0 0 1rem;
  padding: 0.5rem 0.85rem;
  border-inline-start: 3px solid var(--bad);
  border-radius: var(--radius);
  background: var(--bad-soft);
  font-size: var(--text-ui);

  & p { margin: 0 0 0.2rem; font-weight: 600; color: var(--ink); }
  & ul { margin: 0; padding: 0; list-style: none; }
  & li + li { margin-top: 0.15rem; }
  & a { font-weight: 600; color: var(--bad); }
}

.form-row {
  margin-bottom: 1rem;

  & > label {
    display: block;
    font-size: var(--text-small);
    font-weight: 550;
    margin-bottom: 0.25rem;
  }
  & .help {
    display: block;
    font-size: var(--text-label);
    color: var(--ink-soft);
    margin-top: 0.25rem;
  }
}

/* A checkbox reads better with the label beside it. */
.form-row:has(> input[type="checkbox"]) {
  display: flex;
  flex-wrap: wrap;
  align-items: start;
  gap: 0.5rem;

  & > label { order: 2; font-weight: 400; margin: 0; }
  & > input { order: 1; margin-top: 0.3rem; }
  & .help { order: 3; flex-basis: 100%; }
  & > .errorlist { order: 4; flex-basis: 100%; }
}

/* A field with core/js/scan.js's "Scan" button beside it: the box keeps its
   usual width and the button sits at its end, on the same line. Unfilled,
   as .dialog-cancel is: it helps fill the field, and is not the form's action. */
.scan-field {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  max-width: 32rem;

  & > input { flex: 1; min-width: 0; }
  & > .scan {
    flex: none;
    background: transparent;
    color: var(--accent);
  }
}

/* What the camera sees, while scan.js looks for a code in it. */
.scanner-dialog video {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: black;
  border-radius: var(--radius-control);
}

/* A person's profile photograph, on the form that edits them: the picture on
   file, and beside it the two things there are to do to it. See
   core/widgets/person_photo.html for the markup, which replaces Django's
   filename-and-a-tickbox with this.

   The square is the same shape .portrait is on the detail page, at a size
   that belongs on a form -- big enough to recognise a face in, small enough
   that the field beside it is still the field. It wraps onto its own line on
   a narrow screen rather than squeezing the file picker into nothing. */
.photo-field {
  display: flex;
  align-items: start;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.photo-field-current {
  flex: none;
  display: block;
  width: 5.5rem;
  height: 5.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  overflow: hidden;

  & img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

.photo-field-choices {
  /* Grows into whatever the picture leaves, and takes a line of its own
     below 14rem of it -- a file input with no room is unusable. */
  flex: 1 1 14rem;
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.5rem;
}

/* The label holds its own box, so the words are part of what is clicked. */
.photo-field-remove {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--text-small);
  color: var(--ink-soft);

  & input[type="checkbox"] { margin: 0; }
}

/* Files held from the last time a form was sent: one ticked box each, above
   the picker -- see core/widgets/holding_file_input.html. */
.held-uploads {
  list-style: none;
  margin: 0 0 0.5rem;
  padding: 0;

  & label {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    overflow-wrap: anywhere;
  }

  & input[type="checkbox"] { margin: 0; }
}

/* Every native control -- a dropdown's open list, a checkbox, a radio, a range
   -- is drawn by the browser against the desktop's accent colour, which is
   orange here. Naming ours makes the highlight teal like the rest of the
   application. */
input, select, textarea, option {
  accent-color: var(--accent);
}

/* The list a <select> opens is the browser's own; this is the one hook it
   honours in the browsers that let CSS in at all. */
option:checked {
  background: var(--accent);
  color: var(--paper);
}

input, select, textarea {
  font: inherit;
  /* After the shorthand, which resets it. A field matches the buttons beside
     it rather than standing a size above everything else on the form. */
  font-size: var(--text-ui);
  color: inherit;
  width: 100%;
  max-width: 32rem;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--paper);
}
fieldset > fieldset {
  max-width: 32rem;
}
input[type="checkbox"], input[type="radio"] { width: auto; }
input[type="file"]::file-selector-button {
  font: inherit;
  font-size: var(--text-small);
}
textarea { resize: vertical; }

input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* A group of checkboxes or radios: Django wraps the options in a div of their
   own, and each option in a div inside that. The wrapper is what becomes the
   grid, so the eight age ranges a foster will take read as one set in columns
   rather than as eight lines down the page.

   The words beside each box are the field's value, not its label, so they are
   the size every other field's value is -- they were a size larger than the
   question above them, which made the question look like a note under the
   list. */
.choice-group > div {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.3rem 1rem;
  align-items: start;

  & > div > label {
    display: flex;
    gap: 0.4rem;
    align-items: center;
    font-weight: 400;
    font-size: var(--text-ui);
  }

  & input[type="checkbox"], & input[type="radio"] {
    margin: 0;
    width: auto;
  }
}

/* Ruled off from the form above it, so the buttons are never crowded against
   the last field — which is what happens on a narrow screen otherwise. */
.submit-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-block-start: 2rem;
  padding-block-start: 1.25rem;
  border-block-start: 1px solid var(--line);
}

/* Sent, and still uploading: see core/js/submit-once.js. */
form[aria-busy="true"] .submit-row button {
  opacity: 0.6;
  cursor: progress;
}

/* Beside a heading instead — .titles-and-tools's tools slot, or
   .animal-head next to the animal's name — rather than below a form: cancel
   the rule and top spacing above, which otherwise pushes the row onto a
   line of its own. */
.titles-and-tools .submit-row,
.animal-head .submit-row {
  margin-block-start: 0;
  padding-block-start: 0;
  border-block-start: 0;
}

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

/* The admin's three kinds: .button is the plain one, .default the strong
   one a form is submitted with, .deletelink the destructive one.

   The same five things are listed again below, for the filled variant and
   for :hover. Deliberately three plain selector lists rather than one :is():
   :is() takes the specificity of its most specific argument, which would
   lift bare `button` from (0,0,1) to (0,1,1) and let this rule beat the
   filled one under it — every plain button would lose its fill, and every
   .deletelink its red. The repetition is what keeps the cascade flat. */
button, input[type="submit"], .button, .deletelink, .object-tools a {
  font: inherit;
  font-size: var(--text-ui);
  font-weight: 550;
  padding: 0.35rem 0.75rem;
  border-radius: var(--radius-control);
  border: 1px solid var(--accent);
  background: transparent;
  color: var(--accent);
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}

.action-icon {
  width: 1em;
  height: 1em;
  flex: none;
  vertical-align: -0.125em;
}

/* One filled button to a form. A change form offers three ways to save, and
   filling them all makes none of them the obvious one. The site's own forms
   submit with a bare <button>, which stays filled. */
button, input[type="submit"].default, .button.default, .object-tools a {
  background: var(--accent);
  color: var(--paper);
}

button:hover, input[type="submit"]:hover, .button:hover,
.deletelink:hover, .object-tools a:hover {
  filter: brightness(1.08);
}

/* A button scaled to a table cell rather than a page -- a way out of a row,
   not a form's own action. A soft fill read as a button only against plain
   white; against a striped row, or the tint a pill beside it might carry,
   it stopped reading as anything. The border and the card's own opaque
   colour are what make it a button regardless of what it sits on. */
.button.small {
  padding: 0.1rem 0.5rem;
  font-size: var(--text-small);
  font-weight: 600;
  border-color: var(--accent);
  background: var(--card);
  color: var(--accent);
}

/* The general hover just brightens, which washes a light fill closer to
   white -- less visible, not more, on a button already pale. Inverted to a
   solid fill instead, the way pressing it is about to make it. */
.button.small:hover {
  background: var(--accent);
  color: var(--paper);
  filter: none;
}

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

/* A row's own delete, the size of the row's other actions: outlined in red
   rather than filled, as .button.small is outlined in the accent. */
.button.small.deletelink {
  border-color: var(--bad);
  color: var(--bad);
}
.button.small.deletelink:hover {
  background: var(--bad-soft);
  color: var(--bad);
}

/* Under a thumbnail, and as wide as it. */
.thumbs .edit-photo.button {
  margin-top: 0.25rem;
  text-align: center;
}

button.link {
  background: none;
  border: none;
  padding: 0;
  color: var(--accent);
  font-weight: inherit;
  font-size: inherit;
  text-decoration: underline;
}

/* A row of links that narrow a list to one kind of thing, with a count each.
   The one in view is marked with aria-current, so the styling follows what a
   screen reader is already being told rather than a class of its own. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);

  & a {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.8rem;
    text-decoration: none;
    color: var(--ink-soft);
    border-bottom: 2px solid transparent;
  }

  & a:hover { color: var(--ink); }

  & a[aria-current] {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 600;
  }
}

/* ---------- filters ---------- */

.toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: end;
  margin-bottom: 1.25rem;

  & input, & select { max-width: 16rem; }
  & label { display: block; font-size: var(--text-label); color: var(--ink-soft); }
}

/* ---------- animal detail ---------- */

/* The animals either side in its litter. */
.litter-steps {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.75rem;
  font-size: var(--text-ui);

  & [rel="next"] { margin-inline-start: auto; }
}

/* A vaccination in the last week, under the animal's name. */
.recent-vaccination {
  margin: 0 0 1rem;
  padding: 0.6rem 0.85rem;
  border-inline-start: 3px solid var(--warn);
  border-radius: var(--radius);
  background: var(--warn-soft);
  font-size: var(--text-ui);
}

/* The photograph, the name and the facts card, in one band: the picture runs
   down the side of both rather than sitting inside the heading. */
.animal-overview {
  display: flex;
  align-items: stretch;
  gap: 1.5rem;
  margin-bottom: 1.5rem;
}

.animal-summary {
  flex: 1;
  min-width: 0;
  margin-top: 0.6rem;
}

.animal-head {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
  justify-content: space-between;
  align-items: start;
  margin-bottom: 1rem;

  & .identity {
    min-width: 0;
  }
}

/* The intake photograph, serving as a profile picture. A fixed width and a
   height taken from the column beside it, so the band's shape does not depend
   on whether a photograph is portrait or landscape — or there at all. */
.portrait {
  position: relative;
  flex: none;
  display: block;
  width: 13rem;
  min-height: 13rem;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  background: var(--card);

  /* Positioned rather than sized at 100%: the anchor's height comes from the
     flex line, which a percentage height on the image cannot resolve against. */
  & img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
  }
}

/* A chosen photo, and the intake photo it stands in for: each fills the box,
   and the switch over its foot says which is shown. */
.portrait > a {
  position: absolute;
  inset: 0;
}
.portrait .intake,
.portrait:has([value="intake"]:checked) .now { display: none; }
.portrait:has([value="intake"]:checked) .intake { display: block; }

.portrait-switch {
  position: absolute;
  inset-inline: 0.5rem;
  bottom: 0.5rem;
  display: flex;
  margin: 0;
  padding: 0.2rem;
  border: 0;
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--card) 85%, transparent);
  font-size: var(--text-small);

  & legend {
    position: absolute;
    clip-path: inset(50%);
    width: 1px;
    height: 1px;
    overflow: hidden;
    white-space: nowrap;
  }
  & label {
    flex: 1;
    padding: 0.2rem 0.5rem;
    border-radius: var(--radius-pill);
    text-align: center;
    cursor: pointer;
  }
  & label:has(:checked) {
    background: var(--accent);
    color: var(--paper);
  }
  & label:has(:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }
  & input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }
}

/* The photograph, and everything that fits beside it, for a page whose
   subject is a person rather than an animal -- a foster's page, an
   adopter's, an application's.

   A float rather than a row, which is the whole point of it: a row is as
   tall as its tallest cell, so a 13rem photograph beside a two-line heading
   left 8rem of nothing under the photo with the page resuming below all of
   it. Floated, the short things that follow the heading -- the invitation
   notice, the note that this person is also an adopter -- fill that room
   instead.

   flow-root on the wrapper so the float is contained: without it the
   photograph hangs out of the bottom and the next heading on the page sits
   across it. */
.profile-overview {
  display: flow-root;
  margin-bottom: 1.5rem;

  & > .portrait {
    float: inline-start;
    margin-inline-end: 1.5rem;
    margin-bottom: 1rem;
  }

  /* Each block beside the photograph has to be its own formatting context,
     or it is not narrowed by the float at all: a plain block box slides
     underneath and only its text is pushed aside, which puts a card's
     background and border under the photo. flow-root is that context and
     nothing else -- it is what `overflow: hidden` was always being used
     for.

     .titles-and-tools and .invite-form are already flex containers,
     which provide their own formatting contexts. */
  & > *:not(.portrait, .titles-and-tools, .invite-form) {
    display: flow-root;
  }

  /* Beside the photograph these are a stack of short notices, not sections
     of a page: the page's own rhythm between them reads as gaps somebody
     forgot to fill. Halved, and the same number between each pair, so the
     column beside the photo reads as one block. */
  & > .titles-and-tools {
    margin-bottom: 0.75rem;
  }

  & > p,
  & > form {
    margin-block: 0;
  }

  /* A card is the one thing here too tall to be sure of fitting: it starts
     beside the photograph, runs past the bottom of it, and from there down
     it is a narrow column with the page empty to its left -- because a box
     beside a float keeps the float's width for its whole height, not just
     the part that is level with it. Cleared, it starts below the photograph
     at the page's full width instead. The short notices above still fill
     the room beside the photo, which is what that room is the size of. */
  & > .module {
    clear: inline-start;
    margin-bottom: 0.75rem;
  }

  /* Nothing below the last card: the wrapper's own margin is the gap. */
  & > :last-child {
    margin-bottom: 0;
  }
}

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 0.75rem 1.5rem;
  margin: 0;

  & dt { font-size: var(--text-label); color: var(--ink-soft); }
  & dd { margin: 0; }

  /* A pair kept together: the label above its own value, and the two of them
     one item of the grid rather than two that happen to be next to each
     other. Without this a dt and its dd are separate items, so they land in
     neighbouring columns -- the label of one fact level with the value of
     the one before it, which is a guessing game on any list longer than a
     line. */
  & .fact,
  & .wide-fact {
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
  }

  /* A fact whose value runs to a sentence or more, such as a decline reason
     — its own row rather than squeezed into an 8rem column meant for a date
     or a status word. */
  & .wide-fact {
    grid-column: 1 / -1;
  }
}

/* An application's answers. A question is a sentence and an answer may be a
   paragraph, a list of pictures or a signature, so these go one pair to a
   row rather than into columns, and each pair is fenced off from the next:
   down a page of thirty questions, the line above a question is what says
   where the answer before it stopped. */
.facts.answers {
  grid-template-columns: 1fr;
  gap: 0;

  & > .fact {
    padding-block: 0.7rem;
    border-top: 1px solid var(--line-soft);
  }

  /* No rule above the first or below the last: the card's own edge is there. */
  & > .fact:first-child {
    border-top: 0;
    padding-top: 0;
  }
  & > .fact:last-child {
    padding-bottom: 0;
  }
}

/* Notes with more after them in the same card: another list, or the form. */
:is(.dated-notes, .record-notes):not(:last-child) { margin-bottom: 1.25rem; }

.facts.contact-facts {
  /* At most four columns, allowing for the three gaps between them. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, max(15rem, calc((100% - 4.5rem) / 4))), 1fr));
}

/* The daily log keeps a compact list below its recording form. */
.recent-log {
  list-style: none;
  margin: 0;
  padding: 0;
  border-inline-start: 2px solid var(--line);
  & > li { position: relative; padding: 0 0 1.25rem 1.25rem; }
  & > li::before {
    content: "";
    position: absolute;
    inset-inline-start: -5px;
    top: 0.5rem;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
  }
  & .when { font-size: var(--text-label); color: var(--ink-soft); font-variant-numeric: tabular-nums; }
  /* The time of day, where one is known: it tells apart two of a day. */
  & .when .at { margin-inline-start: 0.4rem; font-weight: 600; color: var(--ink); }
  & .what { font-weight: 550; }
}

/* A temperature against the normal range for the animal's age. */
[data-temperature="low"] { color: var(--history-blue); }
[data-temperature="normal"] { color: var(--history-green); }
[data-temperature="high"] { color: var(--bad); }

/* A weight against the one before it. */
[data-weight="rising"] { color: var(--history-green); }
[data-weight="falling"] { color: var(--bad); }

/* A stool other than a brown grade 2, or with a flag ticked. */
[data-stool="worrying"] { color: var(--bad); }

/* A cat's latest body condition: the chart's drawing, then the score. */
.facts .body-condition { display: flex; align-items: center; gap: 0.35rem; }

/* ---------- timeline ---------- */

[data-tone] { --event-ink: var(--ink-soft); --event-tint: var(--line-soft); }
[data-tone="teal"] { --event-ink: var(--accent); --event-tint: var(--accent-soft); }
[data-tone="orange"] { --event-ink: var(--history-orange); --event-tint: var(--history-orange-soft); }
[data-tone="blue"] { --event-ink: var(--history-blue); --event-tint: var(--history-blue-soft); }
[data-tone="green"] { --event-ink: var(--history-green); --event-tint: var(--history-green-soft); }
[data-tone="rose"] { --event-ink: var(--history-rose); --event-tint: var(--history-rose-soft); }
[data-tone="purple"] { --event-ink: var(--reserved); --event-tint: var(--reserved-soft); }
[data-tone="amber"] { --event-ink: var(--warn-ink); --event-tint: var(--warn-soft); }

.history-icon { flex: none; vertical-align: middle; }

.timeline {
  list-style: none;
  margin: 0;
  padding: 0;

  & > li {
    position: relative;
    display: grid;
    grid-template-columns: 5.5rem minmax(0, 1fr);
    gap: 2rem;
    padding-block-end: 1rem;
  }
  & > li::before {
    content: "";
    position: absolute;
    inset-inline-start: 6.5rem;
    inset-block: 0;
    width: 1px;
    background: var(--line);
  }
  & > li::after {
    content: "";
    position: absolute;
    inset-inline-start: calc(6.5rem - 4px);
    top: 1.6rem;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--event-ink);
  }
  & .when {
    display: flex;
    flex-direction: column;
    padding-block-start: 1rem;
    font-size: var(--text-label);
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    & strong { font-size: 1.4rem; color: var(--ink); }
    /* The time of day, where one is known: it tells apart two of a day. */
    & .at { font-weight: 600; color: var(--ink); }
    /* How old the animal was then: under the date, or beside it on a hover. */
    & .age {
      & strong { font-size: inherit; font-weight: 600; color: var(--ink); }
    }
  }
  & .timeline-entry {
    display: grid;
    grid-template-columns: 3rem minmax(0, 1fr) auto;
    align-items: start;
    gap: 1rem;
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
  }
  & .event-icon {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    border-radius: var(--radius-control);
    background: var(--event-tint);
    color: var(--event-ink);
    & svg { width: 1.8rem; height: 1.8rem; }
  }
  & .event-content { min-width: 0; overflow-wrap: anywhere; }
  & .what { font-weight: 600; }
  /* What was recorded with the event, a line each: its name, then it. */
  & .parts {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.1rem 0.75rem;
    margin: 0.3rem 0 0;
    font-size: var(--text-ui);
    & dt { color: var(--ink-soft); }
    & dd { margin: 0; }
  }
  & .kind {
    margin-block-end: 0.25rem;
    font-size: var(--text-micro);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--event-ink);
  }
  & .note {
    margin-block-start: 0.35rem;
    color: var(--ink-soft);
    font-size: var(--text-ui);
    white-space: pre-wrap;
  }
  & .row-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    & a { display: inline-flex; align-items: center; gap: 0.3rem; min-height: 2.25rem; }
    & svg { width: 1rem; height: 1rem; }
  }
  /* An age turned, between the events either side: a ring on the line, and
     the age in a pill beside it. */
  & > li.milestone {
    align-items: center;
    font-size: var(--text-label);
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
    & span {
      justify-self: start;
      padding: 0.15rem 0.6rem;
      border: 1px solid var(--line);
      border-radius: var(--radius-pill);
      background: var(--card);
      font-weight: 600;
      color: var(--ink);
    }
  }
  & > li.milestone::after {
    top: calc(50% - 0.5rem - 5px);
    width: 10px;
    height: 10px;
    inset-inline-start: calc(6.5rem - 5px);
    border: 2px solid var(--ink-soft);
    background: var(--card);
    box-sizing: border-box;
  }
}

/* Where there is a pointer to hover with and room beside the date, the age
   waits there until the date is hovered. Hidden by opacity alone, so a screen
   reader still reads it. */
@media (hover: hover) and (width > 40rem) {
  .timeline .when { position: relative; }
  .timeline .when .age {
    position: absolute;
    z-index: 1;
    inset-inline-start: calc(100% + 0.6rem);
    top: 0.9rem;
    padding: 0.35rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
    font-size: var(--text-ui);
    color: var(--ink-soft);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.12s;
  }
  .timeline .when:hover .age { opacity: 1; }
}

@media (max-width: 60rem) {
  .timeline .timeline-entry { grid-template-columns: 3rem minmax(0, 1fr); }
  .timeline .row-actions { grid-column: 2; }
}

@media (max-width: 40rem) {
  #history { scroll-margin-block-start: calc(var(--topbar-h) + 1rem); }
  .timeline {
    & > li { grid-template-columns: minmax(0, 1fr); gap: 0.5rem; padding-inline-start: 1.25rem; }
    & > li::before { inset-inline-start: 4px; }
    & > li::after { inset-inline-start: 0; top: 0.6rem; }
    & .when { flex-direction: row; align-items: baseline; flex-wrap: wrap; gap: 0.4rem; padding: 0; }
    & .when strong { font-size: 1rem; }
    & .timeline-entry { gap: 0.75rem; padding: 0.75rem; grid-template-columns: 2.5rem minmax(0, 1fr); }
    & .event-icon { width: 2.5rem; height: 2.5rem; }
    & .row-actions { grid-column: 1 / -1; justify-content: end; }
    & > li.milestone { display: flex; gap: 0.5rem; }
    & > li.milestone::after { inset-inline-start: -0.5px; }
    & .age::before { content: "· "; }
  }
}

.thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.4rem;
  padding: 0;
  list-style: none;

  & img {
    width: 88px;
    height: 88px;
    object-fit: cover;
    border-radius: var(--radius-control);
    border: 1px solid var(--line);
  }
  & .edit-photo {
    display: block;
    font-size: var(--text-small);
  }
  & .caption {
    display: block;
    max-inline-size: 88px;
    font-size: var(--text-small);
    color: var(--ink-soft);
  }
}

/* The gallery's squares, drawn from the profile thumbnail: as many to a row
   as fit, two on a phone. 10rem fits six across --measure, so a page of 48
   ends on a full row there. */
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(10rem, 40vw), 1fr));
  gap: 1rem;
  padding: 0;
  list-style: none;

  & img {
    display: block;
    inline-size: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-control);
    border: 1px solid var(--line);
  }
  & p {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: 0 0.5rem;
    margin: 0.3rem 0 0;
    font-size: var(--text-small);
  }
  & time, & .caption { color: var(--ink-soft); }
  & .kind {
    justify-content: start;
    align-items: center;
    gap: 0.3rem;
    color: var(--event-ink);
  }
  & .kind .history-icon { inline-size: 1rem; block-size: 1rem; }
  & form { margin-top: 0.3rem; }
  & .profile { justify-content: start; font-weight: 600; color: var(--accent); }
}

/* A signature, shown whole rather than as one of the squares above: cropping
   one to a thumbnail would cut the ends off a name. It carries its own white
   ground -- see signature.js -- so it is not given a background here, and it
   is bordered like the thumbnails beside it so that it reads as a picture and
   not as part of the page. */
.signature-image {
  display: block;
  width: 100%;
  max-width: 22rem;
  height: auto;
  margin-top: 0.4rem;
  border-radius: var(--radius-control);
  border: 1px solid var(--line);
}

/* ---------- weight chart ---------- */

/* A <figure>, which the browser gives wide margins of its own; inside a card
   it supplies none of its own spacing. */
.weight-chart { margin: 0; }

/* The dashboard's charts, one card to an animal: two to a row where there
   is room, each wide enough to read. */
.weight-charts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
  gap: 0.75rem;
  padding: 0;
  list-style: none;

  & > .module { margin-bottom: 0; }
}

.chart {
  width: 100%;
  height: auto;
  max-height: 14rem;
  overflow: visible;

  & .line { fill: none; stroke: var(--accent); stroke-width: 2; }
  & .dot { fill: var(--accent); }
  & .axis { stroke: var(--line); stroke-width: 1; }
  & text { font-size: 10px; fill: var(--ink-soft); }
}

/* ---------- arranging the dashboard ---------- */

/* One card to a section, its title and what it shows beside the buttons
   that move it, and under them on a phone. A section put away stays in
   its place, outlined only, with the one button that adds it back. */
.layout-sections {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;

  & > li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin-bottom: 0.5rem;
    padding: 0.75rem 1rem;
  }
  & > li.removed {
    border-style: dashed;
    background: transparent;
    color: var(--ink-soft);
  }
  & > li.removed :is(.move, .hide-section),
  & > li:not(.removed) :is(.show-section, .removed-note) { display: none; }
  & > li.removed .drag-handle { visibility: hidden; }
  & > li.dragging {
    position: relative;
    z-index: 1;
    border-color: var(--accent);
    box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.2);
  }
}

.layout-section {
  flex: 1 1 14rem;
  display: grid;
  gap: 0.15rem;
}

.layout-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;

  & button:not(.default) {
    background: var(--card);
    color: var(--accent);
  }
  & button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
}

/* Held to drag. It takes the touch for itself, so a finger on it moves the
   section and not the page. */
.drag-handle {
  padding: 0.25rem;
  font-size: 1.25rem;
  line-height: 1;
  color: var(--ink-soft);
  cursor: grab;
  touch-action: none;
  user-select: none;

  .dragging & { cursor: grabbing; }
}

.layout-status { min-height: 1.5em; color: var(--ink-soft); font-size: var(--text-ui); }
.dashboard-layout button[name="reset"] { background: transparent; color: var(--accent); }

/* ---------- quick add ---------- */

/* Links to add an event, not filters on the timeline below -- styled as the
   same secondary .button.small used for a row's own actions, so they read
   as actions rather than as a set of tags to pick between. */
.quick-add {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1.5rem;
  padding: 0;
  list-style: none;
}

/* One group of related event types, so the menu reads as a handful of
   short rows rather than one long alphabet soup of pills. */
.quick-add-group {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.4rem;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-control);
}

/* Keep the icons within the existing button line height. */
.quick-add .history-icon {
  color: var(--event-ink);
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
}
.quick-add a:hover .history-icon { color: inherit; }

/* ---------- history ---------- */

.history-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-block-end: 1rem;
  & .tabs { margin: 0; }
}

.event-menu {
  position: relative;
  margin-inline-start: auto;
  & > summary {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    list-style: none;
  }
  & > summary .history-icon { width: 1em; height: 1em; }
  &[open] > summary {
    background: var(--accent-strong);
    border-color: var(--accent-strong);
    box-shadow: inset 0 2px 3px rgb(0 0 0 / 0.2);
  }
  & > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
  & .quick-add {
    position: absolute;
    z-index: 10;
    inset-inline-end: 0;
    width: min(36rem, calc(100vw - 3rem));
    margin-block-start: 0.5rem;
    padding: 1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    box-shadow: 0 4px 12px rgb(0 0 0 / 0.12);
  }
}

/* Anchor the litter's menu to its toolbar when the buttons wrap. */
.object-tools:has(.event-menu) { position: relative; }
.object-tools .event-menu {
  position: static;
  & .quick-add { min-inline-size: 18em; max-inline-size: 100%; }
}

/* The kinds to narrow the history to, as chips that toggle: round where the
   "+" buttons above are square-cornered, and quiet until chosen, when they
   fill and take a tick. The tick box itself is hidden but kept, so the
   keyboard and the form still work through it. */
.history-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 1.25rem;

  & fieldset { display: contents; }
  & legend, & input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  & label, & .history-all {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.4rem 0.75rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-pill);
    background: var(--card);
    color: var(--ink-soft);
    font-size: var(--text-small);
    cursor: pointer;
    user-select: none;
  }
  & .history-all { text-decoration: none; }
  & .history-icon { width: 1.25rem; height: 1.25rem; color: var(--event-ink); }
  & .history-all[aria-current] { background: var(--accent-row); color: var(--accent-strong); font-weight: 600; }
  & label:hover, & .history-all:hover { border-color: var(--accent); color: var(--ink); }
  & label:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
  & label:has(:checked) {
    border-color: var(--accent);
    background: var(--accent-row);
    color: var(--accent-strong);
    font-weight: 600;
  }
  & label:has(:checked)::before {
    content: "\2713";
    content: "\2713" / "";
  }
  & .count { opacity: 0.75; font-variant-numeric: tabular-nums; }
  & button, & .button { font-size: var(--text-small); padding: 0.25rem 0.75rem; }
  /* The button rule's display would otherwise outrank [hidden]'s. */
  & [hidden] { display: none; }
}

/* A day to a row: each reading with its change since the day before under
   it, a loss in the colour for "wrong" because a kitten losing weight is. */
.history-days {
  & th[scope="row"] { white-space: nowrap; }
  & .change, & .was {
    display: block;
    font-size: var(--text-label);
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
  }
  & .change.falling { color: var(--bad); }
  & .change.rising { color: var(--history-green); }
  & .not-given { color: var(--ink-faint); }
  /* Several of one thing in a day: a line each, and when each was. */
  & .timed { display: block; white-space: nowrap; }
  & .at {
    margin-inline-start: 0.2rem;
    font-size: var(--text-label);
    font-weight: 400;
    color: var(--ink-soft);
    font-variant-numeric: tabular-nums;
  }
}

/* A litter's week: an animal to a row and a day to a column, newest first.
   The animal stays put while the days scroll under it, and the columns are
   narrow enough that a phone shows three of them. */
.daily-log {
  & th[scope="row"] {
    position: sticky;
    inset-inline-start: 0;
    max-inline-size: 6rem;
    white-space: normal;
    background: var(--card);

    & .age {
      display: block;
      font-size: var(--text-label);
      font-weight: 400;
      color: var(--ink-soft);
    }
    /* Its status, under its age. */
    & .pill { margin-block-start: 0.2rem; font-weight: 400; }
  }
  & td, & thead th { min-inline-size: 5.5rem; }
  /* Each litter's heading, where the log spans several. */
  & .litter-group th {
    padding-block-start: 1rem;
    background: var(--line-soft);
    text-align: start;
  }

  /* Each reading is a pill in its kind's timeline colour, which is what
     tells a weight from a temperature from a dose where one cell holds all
     three. A reading already coloured for what it says keeps that colour. */
  & td > [data-tone] {
    inline-size: fit-content;
    margin-block: 0.2rem;
    padding: 0.15rem 0.75rem;
    /* A card's corners, not a pill's: a long dose runs to several lines, and
       a pill's radius rounds those into an oval the words stick out of. */
    border-radius: var(--radius);
    background: var(--event-tint);
    font-weight: 550;

    /* What a reading is stands out from what is said about it: the weight is
       the heavier, and its change follows beside it, lighter and after a
       rule, so the two numbers are not read as one. */
    & .change, & .was { font-weight: 400; }
    & .change {
      display: inline;
      margin-inline-start: 0.2rem;
      padding-inline-start: 0.45rem;
      border-inline-start: 1px solid;
    }
  }
  & td > [data-tone]:not([data-weight], [data-temperature], [data-stool], .not-given) { color: var(--event-ink); }
  /* It reaches into the padding either side, so it fits the name's column. */
  & img {
    display: block;
    inline-size: 5rem;
    margin-inline: -0.2rem;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-control);
  }
}

/* ---------- pagination ---------- */

.paginator {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  align-items: center;
  justify-content: center;
  margin-top: 1.5rem;
  font-size: var(--text-ui);

  /* An <ol> in the site's pagination, a <ul> in the admin's. */
  & ol,
  & ul {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  & a,
  & .current,
  & .step {
    display: inline-block;
    padding: 0.3rem 0.6rem;
    border-radius: var(--radius-control);
    line-height: 1;
  }

  & li a,
  & li .current,
  & li .gap {
    min-width: 2.1rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
  }

  & a {
    color: var(--ink-soft);
    border: 1px solid transparent;
    text-decoration: none;
  }
  & a:hover {
    color: var(--accent);
    border-color: var(--line);
    background: var(--card);
  }

  & .current {
    background: var(--accent);
    border: 1px solid var(--accent);
    color: var(--paper);
    font-weight: 600;
  }

  & .gap {
    display: inline-block;
    padding: 0.3rem 0.2rem;
    color: var(--ink-faint);
  }

  /* Previous and Next when there is nowhere to go: present, but plainly inert. */
  & .disabled {
    color: var(--ink-faint);
    cursor: default;
  }
}

/* The size of the result set, under the table. Quieter than the paginator
   above it: it is there to be looked up, not read. */
.result-count {
  margin-top: 0.75rem;
  text-align: center;
  font-size: var(--text-small);
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* Offered under a list once its sort is remembered: see sort-memory.js. */
.sort-reset {
  margin-top: 0.25rem;
  text-align: center;
  font-size: var(--text-small);

  & button.link { color: var(--ink-faint); }
}

/* ---------- login ---------- */

.login-logo {
  display: block;
  width: 18rem;
  max-width: 100%;
  height: auto;
  margin-bottom: 1rem;
}

.narrow {
  max-width: 26rem;
  margin-inline: auto;
  padding-top: 3rem;
}

/* ---------- editing a photo ---------- */

.photo-turn {
  display: flex;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

/* The photo, with the crop drawn over it by photo-crop.js: everything
   outside the box shaded, which is what the crop leaves out. */
.crop-area {
  position: relative;
  inline-size: fit-content;
  overflow: hidden;
  margin-bottom: 1rem;
  border-radius: var(--radius-control);

  & img {
    display: block;
    max-inline-size: 100%;
    max-block-size: 70vh;
  }
}
.crop-box {
  position: absolute;
  box-shadow: 0 0 0 100vmax rgb(0 0 0 / 0.55);
  outline: 2px solid #fff;
  cursor: move;
  touch-action: none;

  & .handle {
    position: absolute;
    inline-size: 1.5rem;
    block-size: 1.5rem;
    background: #fff;
    border: 2px solid var(--accent);
    border-radius: 50%;
  }
  & [data-corner="nw"] { top: -2px; left: -2px; cursor: nwse-resize; }
  & [data-corner="ne"] { top: -2px; right: -2px; cursor: nesw-resize; }
  & [data-corner="sw"] { bottom: -2px; left: -2px; cursor: nesw-resize; }
  & [data-corner="se"] { bottom: -2px; right: -2px; cursor: nwse-resize; }
}

/* The four numbers side by side, the square box under them. */
.photo-crop .module {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: 0 1rem;

  & > :is(.form-row:has([type="checkbox"]), .errorlist) { grid-column: 1 / -1; }
}

/* Adding photos, the second step: each photo beside its caption. */
.photo-captions {
  display: grid;
  gap: 1rem;
  margin: 0 0 1rem;
  padding: 0;
  list-style: none;

  & > li {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: start;
  }
  & img {
    inline-size: 10rem;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--radius-control);
  }
  & .photo-fields { flex: 1 1 14rem; }
  & textarea { inline-size: 100%; }
}

/* A photo's round x, over its top corner -- see _remove_photo.html. */
.thumbs li,
.photo-frame {
  position: relative;
}
.remove-photo {
  position: absolute;
  top: -0.5rem;
  right: -0.5rem;
  display: grid;
  place-items: center;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink-soft);
  font-size: 1.1rem;
  line-height: 1;
  text-decoration: none;

  &:hover, &:focus-visible {
    border-color: var(--bad);
    background: var(--bad);
    color: #fff;
  }
}

/* The x on the corner of a medication or a reminder added to a page, which
   takes it off again: see medication/js/medication-rows.js and reminders.js.
   Centred on the box's rounded corner, where the curve is 29% of the radius
   in from each side, and filled so that it reads as an action. Placed
   within a positioned fieldset, whose box starts below its legend: the top
   border runs through the legend's middle, about 12px higher, so the top
   is set by eye to sit on it. */
button.remove-entry,
.photo-dialog .dialog-close {
  --size: 1.5rem;
  position: absolute;
  top: -1.25rem;
  right: calc(var(--radius) * 0.29 - var(--size) / 2);
  display: grid;
  place-items: center;
  inline-size: var(--size);
  block-size: var(--size);
  padding: 0;
  border: 2px solid var(--card);
  border-radius: 50%;
  background: var(--bad);
  color: #fff;
  box-shadow: 0 1px 3px rgb(0 0 0 / 30%);

  &:hover, &:focus-visible { filter: brightness(0.88); }
}

/* Centre the X geometrically, independently of the font's baseline. */
.remove-photo,
button.remove-entry,
.photo-dialog .dialog-close {
  &::before, &::after {
    content: "";
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: 0.65rem;
    block-size: 2px;
    background: currentColor;
    rotate: 45deg;
  }
  &::after { rotate: -45deg; }
}

/* Removal icons stay red, including inside destructive buttons. */
.delete-icon { color: var(--bad); }
.remove-action::before {
  content: "";
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  background: var(--bad);
  mask: url("../icons/delete.a447f1c103dd.svg") center / contain no-repeat;
}
.remove-action:has(.action-label)::before { margin-inline-end: 0.35em; }
button.remove-action,
button:has(> .delete-icon),
a:has(> .delete-icon) {
  background: var(--card);
  color: var(--bad);
  border-color: var(--bad);

  &:hover { background: var(--bad-soft); color: var(--bad); }
}

@media (max-width: 40rem) {
  /* Keep the action's name available to assistive technology. */
  .action-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
  .remove-action:has(.action-label)::before { margin-inline-end: 0; }
}

/* A litter's animals, each by a thumbnail of its intake photo that
   core/js/photo-dialog.js opens large, or a blank tile of the same size where
   there is none, so every name starts in the same place. The row is centred
   on it: hung from the top, the text looks detached from the photo. */
table.batch-animals td { vertical-align: middle; }
.batch-photo {
  /* Narrow, so the column is no wider than its padding made it for a smaller photo. */
  padding: 0 0.2rem;

  & img, &:empty::before {
    display: block;
    inline-size: 5rem;
    aspect-ratio: 1;
    border-radius: var(--radius-control);
  }
  & img { object-fit: cover; }
  &:empty::before { content: ""; background: var(--line); }
}

/* The photo is the whole dialog, so a click on the dialog itself is a click
   on the backdrop, outside it. Its x sits on the top right curve, as the x
   on a card does. */
.photo-dialog {
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--card);
  overflow: visible;

  & > form { display: contents; }
  & .dialog-close { top: calc(var(--radius) * 0.29 - var(--size) / 2); }
  & img {
    display: block;
    max-inline-size: min(48rem, 92vw);
    max-block-size: 85dvh;
    border-radius: var(--radius);
  }
  &::backdrop { background: rgb(0 0 0 / 60%); }
}

/* The photo being removed, on the page that asks first. */
img.removing {
  display: block;
  inline-size: 8rem;
  aspect-ratio: 1;
  object-fit: cover;
  margin-bottom: 0.5rem;
  border-radius: var(--radius-control);
}

.photo-profile,
.photo-undo {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* ---------- small screens ---------- */

@media (max-width: 40rem) {
  .facts { grid-template-columns: repeat(2, 1fr); }

  /* Stacked, the picture would otherwise stretch the full width of the page. */
  .animal-overview { flex-direction: column; }

  /* Too narrow to sit anything beside a 13rem photograph: the float would
     leave a few words to a line. The photo goes back to being a block of its
     own, with the page at full width under it. */
  .profile-overview > .portrait {
    float: none;
    margin-inline-end: 0;
  }
  .portrait {
    width: 100%;
    max-width: 15rem;
    aspect-ratio: 1;
    min-height: 0;
  }
}

@media print {
  body:has(nav.navbar) #container { padding: 0; }
  nav.navbar, .quick-add, .submit-row, .row-actions, .toolbar, .object-tools { display: none; }
  body { background: #fff; }
}

/* Approving or declining an application. Approve is one button; declining
   opens a dialog carrying the reason it has to give. The dialog ships open in
   the markup so that a browser running no script shows the box inline, which
   is why the layout below has to read sensibly either way. */
.decision {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;

  & button { white-space: nowrap; }

  /* Approved: nothing left to press. Greyed and inert, rather than gone --
     a decision already made is worth seeing on the row, not just its
     absence. */
  & button:disabled {
    opacity: 0.4;
    cursor: not-allowed;
  }
  & button:disabled:hover { filter: none; }

  /* Declined: the one door that stays open, so it stays pressable -- only
     greyed, to say the application already has an answer without saying
     the answer cannot be revisited. */
  & .decline-open.decided {
    opacity: 0.55;
  }

  /* Undo: the size of the decision beside it, but outlined, as the quieter
     of the two. */
  & .undo-open {
    background: var(--card);
    color: var(--accent);
  }

  & label {
    display: block;
    font-size: var(--text-label);
    color: var(--ink-soft);
    margin-bottom: 0.2rem;
  }

  /* One size, whatever is typed into it. A box that grows under the cursor
     moves the buttons while somebody is reaching for them, and a dialog that
     grows with it changes shape on the screen as they write. It scrolls
     instead, which nothing else on the page has to notice.

     Dragged larger is a different matter: that is somebody asking for the
     room, rather than the room being taken from under them. */
  & textarea {
    width: 100%;
    min-width: 14rem;
    height: 5lh;
    resize: vertical;
    overflow-y: auto;
  }
}

/* On a phone, an application's buttons sit at the right, where a thumb is.
   In a queue's card the cell holding them gets a line of its own, rather
   than running on after the summary with a separating dot. */
@media (max-width: 47.99rem) {
  .decision { justify-content: end; }

  table.stacked-results td:has(> .decision) {
    display: block;
    margin-top: 0.4rem;
  }
  table.stacked-results td:has(> .decision)::before { content: none; }
}

.decline-dialog,
.approve-dialog,
.undo-dialog,
.scanner-dialog,
.remove-photo-dialog {
  padding: 1.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  /* A width rather than a max-width, so the dialog is the same size before
     anything is typed as after. */
  width: min(24rem, 92vw);
  /* A ceiling as well. Nothing inside grows as anyone types, so this is only
     for a small window and a validation message: the buttons stay reachable
     rather than being pushed off the bottom. */
  max-height: 85dvh;
  overflow-y: auto;

  & h2 {
    margin: 0 0 0.75rem;
    font-size: var(--text-base);
  }

  &::backdrop {
    background: rgb(0 0 0 / 45%);
  }
}

/* Open in the flow, for a browser with no script: no border of its own, since
   it is sitting inside the row rather than over it. */
.decision > .decline-dialog[open]:not(:modal),
.decision > .approve-dialog[open]:not(:modal),
.decision > .undo-dialog[open]:not(:modal) {
  position: static;
  display: flex;
  padding: 0;
  border: 0;
  background: none;
  width: auto;
  max-width: none;
  max-height: none;

  /* The heading and the warning are the dialog explaining itself. Inline in
     a table cell there is no dialog to explain, and the button beneath them
     is the one that was always there. */
  & h2, & p { display: none; }

  /* A cell in a row rather than a panel over the page, so it takes less
     room; it is still a fixed box. */
  & textarea { height: 3lh; }

  /* Tighter than over the page, for the same reason. */
  & .dialog-actions { margin-top: 0.4rem; }
}

/* Except Undo's warning, which is the reason the box is there at all. */
.decision > .undo-dialog[open]:not(:modal) p {
  display: block;
  margin: 0;
  max-width: 18rem;
  font-size: var(--text-label);
  color: var(--ink-soft);
}

.dialog-actions {
  display: flex;
  align-items: center;
  justify-content: end;
  gap: 0.75rem;
  margin-top: 0.75rem;
}

/* The quiet half of the pair. A bare <button> is filled, which would make
   Cancel look like the thing to press; unfilled, it is plainly the way out
   and Decline is plainly the action. */
.dialog-cancel {
  background: transparent;
  color: var(--accent);
}

/* ---------- stool ---------- */

/* The seven grades as buttons, each with its drawing: as many a row as fit,
   and never more than seven. The radio itself is hidden but still the thing
   focused and read out; the button around it is its label. */
.choice-group > .stool-grades {
  grid-template-columns: repeat(auto-fill, minmax(max(4.5rem, (100% - 2.4rem) / 7), 1fr));
  gap: 0.4rem;

  & > label {
    position: relative;
    display: grid;
    justify-items: center;
    align-content: start;
    gap: 0.2rem;
    padding: 0.5rem 0.25rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    font-size: var(--text-label);
    font-weight: 400;
    line-height: 1.25;
    text-align: center;
    cursor: pointer;
  }
  & > label:has(:checked) {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: inset 0 0 0 1px var(--accent);
  }
  & > label:has(:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
  /* Only where the button can show it is chosen instead. */
  @supports selector(:has(*)) {
    & input[type="radio"] { position: absolute; inset: 0; opacity: 0; }
  }
  & img { inline-size: 3.5rem; block-size: auto; }
  & strong { font-size: var(--text-ui); }
}

/* The nine body condition scores, and at intake "Not scored", in one row of
   buttons, each with the chart's cat from above. The labels step aside so
   that the chosen score's guide and side view, inside its label, can take
   the whole row beneath them. Asked only of a cat: the species select marks its option, and the
   question is hidden while another is chosen. */
.choice-group > .body-conditions {
  grid-template-columns: repeat(9, minmax(0, 1fr));
  align-items: stretch;
  gap: 0.2rem;
  max-inline-size: 32rem;

  & > label { display: contents; }
  & > label > span {
    position: relative;
    display: grid;
    justify-items: center;
    align-content: space-between;
    gap: 0.2rem;
    padding: 0.35rem 0.1rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    font-size: var(--text-label);
    font-weight: 400;
    line-height: 1.15;
    text-align: center;
    cursor: pointer;
  }
  &:has(input[value=""]) { grid-template-columns: min-content repeat(9, minmax(0, 1fr)); }
  & > label:has(input[value=""]) > span { align-content: center; }
  & > label:has(:checked) > span {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: inset 0 0 0 1px var(--accent);
  }
  & > label:has(:focus-visible) > span { outline: 2px solid var(--accent); outline-offset: 2px; }
  & > label > span img { inline-size: min(100%, 2rem); block-size: auto; }
  & > label > small img { display: block; margin-block-start: 0.35rem; }
  & strong { font-size: var(--text-ui); }
  & > label > small { display: none; grid-area: 2 / 1 / 3 / -1; font-size: var(--text-small); }
  @supports selector(:has(*)) {
    & input[type="radio"] { position: absolute; inset: 0; opacity: 0; }
    & > label:has(:checked) > small { display: block; }
  }
}
@supports selector(:has(*)) {
  form:has([data-feline]):not(:has([data-feline]:checked)) .choice-group:has(> .body-conditions) {
    display: none;
  }
}

.stool-guide {
  margin-block: 0.5rem;
  font-size: var(--text-small);

  & dl { display: grid; gap: 0.25rem; margin: 0.5rem 0; }
  & dl > div { display: flex; align-items: center; gap: 0.75rem; }
  & dt { display: flex; flex: 0 0 7.5rem; align-items: center; gap: 0.5rem; font-weight: 600; }
  & dd { margin: 0; }
  & img { inline-size: 3rem; block-size: auto; }
}

/* Several of one animal's fields behind one button on a litter's batch
   page -- see _batch_group.html. The button fills in once a box in it is
   ticked. Without popovers there is no button, and the fields sit in the
   cell. */
.batch-group { display: none; }
.batch-group:has(+ .batch-group-fields :checked) { background: var(--accent); color: var(--paper); }
.batch-group-fields {
  display: grid;
  gap: 0.4rem;

  & label { display: flex; align-items: center; gap: 0.4rem; font-weight: 400; }
  & input { width: auto; margin: 0; }
  & h2, & > button { display: none; }
}
@supports selector(:popover-open) {
  .batch-group { display: inline-flex; }
  .batch-group-fields {
    width: min(20rem, 92vw);
    padding: 1.25rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    color: var(--ink);

    &:not(:popover-open) { display: none; }
    & h2 { display: block; margin: 0 0 0.25rem; font-size: var(--text-base); }
    & > button { display: inline-flex; justify-self: end; margin-block-start: 0.5rem; }
    &::backdrop { background: rgb(0 0 0 / 45%); }
  }
}

/* What to do about a worrying stool: shown once the form holds one, by its
   grade, colour, a flag or the appetite. A milk-fed kitten's grade is not
   worrying: see bottlebabies.forms.FeedForm. A browser without :has() always
   shows it. */
.stool-advice {
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  background: var(--brand-soft);
  color: var(--on-brand);
  font-size: var(--text-small);
}
@supports selector(:has(*)) {
  form:not(:has(
    .stool-grades :checked:not([value="2"], [data-milk-fed]),
    [data-stool="flag"]:checked,
    [data-stool="colour"] option:checked:not([value=""], [value="brown"]),
    [data-stool="appetite"] option:checked:is([value="reduced"], [value="none"])
  )) .stool-advice { display: none; }
}
