/* 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; }
    }

    & 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: 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); }
}

@media (prefers-reduced-motion: reduce) {
  body.motion-ready nav.navbar :is(ul.nav-links, .menu.close) { 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); }
}

/* ---------- 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;
}

/* "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 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; }
}

.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; }

/* ---------- 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; }
    & 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;
    }
  }
}

/* ---------- 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);
}

.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; }
}

/* 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);
}

/* 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; }
}

/* 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;
  }
}

/* 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;
  }
}

.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; }
  & .what { font-weight: 550; }
}

/* ---------- 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); }
  }
  & .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; }
  & .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; }
  }
}

@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; }
  }
}

.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);
  }
}

/* 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; }

.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); }
}

/* ---------- 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); }
  & .not-given { color: var(--ink-faint); }
}

/* ---------- 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 core/js/medication-rows.js and appointments.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 {
  padding-block: 0.3rem;

  & img, &:empty::before {
    display: block;
    inline-size: 2.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-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;

  /* 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;
  }

  & 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;
  }
}

.decline-dialog,
.approve-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) {
  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; }
}

.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);
}
