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

:root {
  color-scheme: light dark;

  --bg: light-dark(#f3f4f6, #101317);
  --surface: light-dark(#fff, #1b2027);
  --field: light-dark(#f7f8f9, #151a21);
  --border: light-dark(#d9dde3, #2c333d);
  --text: light-dark(#14181d, #e6e9ee);
  --muted: light-dark(#5a6472, #98a2b0);
  --accent: light-dark(#1d5fd0, #6f9ff0);
  --on-accent: light-dark(#fff, #0b0f14);
  --danger: light-dark(#c02626, #e05a5a);
  --error-bg: light-dark(#fdecec, #3a1a1c);
  --error-border: light-dark(#f3b6b6, #7a2f33);
  --radius: 8px;
  /* How wide a single column of a page is allowed to get. A card and the bar
     above it share it, so the two line up at any screen size. */
  --measure: 26rem;
  /* How tall the bar at the top of every page is, kept as a value of its own so
     that the page below it can fill what is left of the screen. */
  --masthead-height: 3.5rem;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font:
    1rem/1.5 system-ui,
    sans-serif;
}

main {
  display: grid;
  align-content: start;
  min-height: calc(100dvh - var(--masthead-height));
  padding: 1.5rem;
}

/* The bar at the top of every page: which portal this is, who is signed in and
   the way out again. It sticks to the top of the screen while the page scrolls,
   so signing out never has to be scrolled back up to. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  min-height: var(--masthead-height);
  padding: 0.5rem 1.5rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.masthead__brand {
  display: flex;
  gap: 0.6rem;
  align-items: center;
  color: inherit;
  text-decoration: none;
}

/* Sized by the bar and not by the image, so a logo of any proportions sits in it
   without deciding how tall the bar is. The corners are rounded because the logo
   comes with a white ground of its own, which is a white block against the dark
   theme unless it is cut to the same shape as everything else in the portal. */
.masthead__logo {
  width: auto;
  height: 2.25rem;
  border-radius: var(--radius);
}

.masthead__title {
  font-weight: 600;
}

.masthead__account {
  display: flex;
  /* min-width:0 on both this and the name in it: without it the account's
     automatic minimum is the full width of the name, and the name could not
     shrink into the ellipsis it is given. */
  min-width: 0;
  gap: 0.75rem;
  align-items: center;
}

/* A family name can be a whole sentence, and it is cut off rather than allowed
   to push the way out of reach on a narrow screen. */
.masthead__name {
  min-width: 0;
  overflow: hidden;
  color: var(--muted);
  font-size: 0.9rem;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.masthead__logout {
  flex: none;
  margin-bottom: 0;
}

/* A button in the bar is sized by its label rather than by a form field, the
   same way the buttons next to a heading are. */
.masthead__logout .button {
  width: auto;
  padding: 0.35rem 0.8rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

.centered {
  align-content: safe center;
}

.card {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding: 1.25rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.hero {
  text-align: center;
}

/* The small label above a heading, which says what the heading is about before
   it has been read. */
.eyebrow {
  margin: 0 0 0.35rem;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.hero h1 {
  margin: 0 0 0.5rem;
  font-size: 1.9rem;
}

.hero__lead {
  margin: 0 0 1.25rem;
  color: var(--muted);
}

h1 {
  margin: 0 0 0.75rem;
  font-size: 1.4rem;
}

p {
  margin: 0 0 1rem;
}

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

form {
  margin-bottom: 1rem;
}

.error {
  margin: 0 0 1rem;
  padding: 0.6rem 0.75rem;
  font-size: 0.9rem;
  background: var(--error-bg);
  border: 1px solid var(--error-border);
  border-radius: var(--radius);
}

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

h2 {
  margin: 1.25rem 0 0.5rem;
  font-size: 1rem;
}

code {
  font-family: ui-monospace, monospace;
}

/* The sign-ins of a year, one family to a line with its password underneath: read
   off the screen and written down, either because there is nowhere to mail it or
   because a provider would not take it. */
.credentials,
.failures {
  margin: 0 0 1rem;
  padding: 0;
  font-size: 0.9rem;
  list-style: none;
}

.credentials li,
.failures li {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
}

.credentials li:last-child,
.failures li:last-child {
  border-bottom: none;
}

/* How far along the mail run is. The bar is drawn by the browser, so this only
   gives it the colour of the portal and the width of the card. */
.progress {
  display: block;
  width: 100%;
  height: 0.4rem;
  margin-bottom: 1rem;
  accent-color: var(--accent);
}

.field {
  margin-bottom: 1rem;
}

.field__label {
  display: block;
  margin-bottom: 0.25rem;
  color: var(--muted);
  font-size: 0.9rem;
}

/* A hint about what a field accepts, sitting under the field it belongs to. */
.field__hint {
  margin: 0.35rem 0 0;
  color: var(--muted);
  font-size: 0.8rem;
}

.field__input {
  width: 100%;
  padding: 0.6rem 0.7rem;
  font: inherit;
  color: inherit;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.field__input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.button {
  display: block;
  width: 100%;
  padding: 0.65rem 1rem;
  font: inherit;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}

.button--primary {
  background: var(--accent);
  color: var(--on-accent);
}

/* For the one action that throws something away rather than adding it. The
   colour says destructive, but it stays an outline rather than a filled block,
   so it does not outshout the year it acts on. */
.button--danger {
  color: var(--danger);
  border-color: var(--danger);
}

/* A secondary action, and the one the buttons sit next to a heading are both
   sized by their label instead of by a form field. */
.button--quiet {
  color: inherit;
  border-color: var(--border);
}

/* The one action a page is for, so it is the largest thing to press on it. */
.button--big {
  padding: 1.1rem 1.25rem;
  font-size: 1.1rem;
}

/* Which of the three screens of the entry flow is being answered. It is a line
   of small type rather than a set of dots or bars, so it costs no height worth
   spending and still says where in the flow this is. */
.steps {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  margin-bottom: 1rem;
  color: var(--muted);
  font-size: 0.8rem;
}

.steps__name {
  font-weight: 600;
  color: var(--text);
}

/* The choices on one screen of the entry flow, a row each rather than a
   dropdown. On a phone a row is something you can hit without aiming, and the
   whole list stays readable without opening anything first. */
.choices {
  display: grid;
  gap: 0.5rem;
  margin: 0 0 1rem;
  padding: 0;
  border: 0;
}

.choices__option {
  display: flex;
  gap: 0.65rem;
  align-items: center;
  /* Tall enough to press with a thumb, which is the whole point of a row. */
  min-height: 2.75rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

/* The chosen row is marked by the radio itself as well as by this, so the state
   is not carried by colour alone. */
.choices__option:has(input:checked) {
  font-weight: 600;
  background: var(--field);
  border-color: var(--accent);
}

.choices__option input {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  accent-color: var(--accent);
}

.choices__option:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

textarea.field__input {
  resize: vertical;
}

/* The hours a family has done, next to the hours the year asks of it. The one is
   the number the page is about, so it is the large one; the other is read
   against it. */
.hours__count {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.4rem;
  align-items: baseline;
  margin-bottom: 0.75rem;
}

.hours__done {
  font-size: 2.6rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.hours__of {
  color: var(--muted);
}

/* The share of the year's hours that is done. The track keeps the same size
   whatever the hours are, so the filled part inside it is the only thing that
   says where a family stands. */
.progress {
  height: 0.7rem;
  margin-bottom: 0.6rem;
  overflow: hidden;
  background: var(--field);
  border: 1px solid var(--border);
  border-radius: 999px;
}

.progress__bar {
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
}

/* The overview is one screen, like the family page: the years stay down the side
   of it, the name of the selected year and what can be done to it stay above the
   families, the column headings stay above the rows, and the rows are the one
   part that scrolls. A year of families is longer than any screen, and both of
   the lists above them are read again while working through it.

   Height rather than min-height, and overflow hidden, so the page as a whole is
   exactly one screen. What the masthead takes off the screen is taken off here. */
.dashboard-page {
  display: flex;
  flex-direction: column;
  height: calc(100dvh - var(--masthead-height));
  overflow: hidden;
}

.overview {
  display: grid;
  flex: 1;
  /* The single row is the height of the page rather than of its content, so that
     both columns have a height to be bounded by. */
  grid-template-columns: minmax(9rem, 13rem) 1fr;
  grid-template-rows: minmax(0, 1fr);
  gap: 1.5rem;
  min-height: 0;
}

/* The years, which stay where they are while the families are read. A portal that
   has been running for a few years has more years than a screen has room for, so
   they scroll themselves; min-height:0 is what lets a grid item be shorter than
   its content instead of pushing the page taller. */
.overview__years {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* The families of the selected year, as a column of the page: the heading with
   its actions and the rows below it, the first two of which stay put. */
.overview__people {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* What stays above the rows and therefore never scrolls: which year this is and
   what can be done to it. The action that belongs to the year shares the row with
   the name, so it ends up in the top right of the overview. */
.overview__head {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
}

/* The rows, which are the one part of the page that scrolls. */
.people__scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  /* Scrolling to the end of the list should not then scroll the page behind it,
     which is what a stray gesture does otherwise. */
  overscroll-behavior: contain;
}

/* The column headings stay above the rows while they scroll under them. They need
   a ground of their own, or the rows they pass over show through the gaps
   between the words. */
.people thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg);
}

.overview__head h1 {
  margin-bottom: 0;
}

/* The actions belonging to the selected year, which a planned year has two of
   and every other year at most one. */
.overview__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: baseline;
}

.overview__head .button {
  width: auto;
  font-size: 0.9rem;
  white-space: nowrap;
}

.years {
  display: grid;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.years__item {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
  padding: 0.5rem 0.65rem;
  color: inherit;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
}

.years__item:hover {
  background: var(--field);
}

/* The selected year is marked with aria-current rather than a class, so that
   assistive technology announces it too. */
.years__item[aria-current] {
  font-weight: 600;
  background: var(--surface);
  border-color: var(--border);
}

/* The entry that adds a year rather than picking one, so it wears the colour of
   a link instead of blending in with the years it sits above. */
.years__item--create {
  color: var(--accent);
  font-weight: 600;
}

.pill {
  padding: 0.05rem 0.45rem;
  font-size: 0.75rem;
  color: var(--muted);
  white-space: nowrap;
  border: 1px solid var(--border);
  border-radius: 999px;
}

.pill--active {
  color: var(--on-accent);
  background: var(--accent);
  border-color: transparent;
}

.overview__quota,
.overview__count {
  margin-left: 0.4rem;
  font-size: 1rem;
  font-weight: 400;
  color: var(--muted);
}

/* The number in front of a row: said as quietly as it can be while still lining
   the rows up, and as narrow as the widest number in it. width:1% with nowrap is
   what makes the column shrink to its content rather than claim its share of the
   table, and no left padding because it sits against the edge of the page. */
.people .people__index {
  width: 1%;
  padding-left: 0;
  color: var(--muted);
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.people {
  width: 100%;
  border-collapse: collapse;
}

.people th,
.people td {
  padding: 0.5rem 0.65rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
}

.people th {
  font-size: 0.75rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.people .numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The hours of a family, with the badge that says whether they have met the
   quota. Inside the cell rather than on it, for the reason given further down. */
.people__hours {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  justify-content: flex-end;
}

/* For a column or a label that is only there to be read out. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The form a row turns into for adding a family. The inputs share the row
   rather than stacking like they do in a form on its own. */
.people__form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: 0;
}

.people__form .people__input {
  flex: 1 1 10rem;
  min-width: 0;
  padding: 0.4rem 0.5rem;
  font-size: 0.9rem;
}

.people__form .button {
  width: auto;
  padding: 0.4rem 0.7rem;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Inside the cell rather than on it: a cell that is itself a flex container is
   no longer a cell, and its border stops lining up with the row. */
.people__actions {
  display: flex;
  gap: 0.75rem;
  justify-content: flex-end;
  font-size: 0.9rem;
  white-space: nowrap;
}

.people__action {
  color: var(--muted);
}

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

.people__action--danger {
  color: var(--danger);
}

.people__action--danger:hover {
  color: var(--danger);
}

/* The family page fills the screen and hands the scrolling to the list of
   entries alone. The count and the way in to recording more are what a family
   came to the page for, and a year of hourly work is longer than any phone — so
   those stay put and the record moves under them.

   Height rather than min-height, and overflow hidden, so the page as a whole is
   exactly one screen and the scrolling happens in the one part meant to scroll.
   What the bar above it takes off the screen is taken off here, so "one screen"
   still means one screen now that there is a bar. */
.hours-page {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  height: calc(100dvh - var(--masthead-height));
  overflow: hidden;
  padding: 1rem;
}

.hours-page .card {
  flex: none;
}

/* The heading of the list stays as well, so the scrolling part is only entries:
   the record is read under a name that says what it is. */
.entries {
  display: flex;
  flex: 1;
  flex-direction: column;
  min-height: 0;
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
}

.entries__head {
  display: flex;
  flex: none;
  gap: 0.5rem;
  align-items: baseline;
  justify-content: space-between;
  padding-top: 0.5rem;
}

.entries__title {
  margin: 0;
  font-size: 1rem;
}

.entries__count {
  color: var(--muted);
  font-size: 0.9rem;
  font-variant-numeric: tabular-nums;
}

/* The scrolling part. min-height:0 is what lets a flex child be smaller than its
   content instead of pushing the page taller, and it is the reason the count
   above stays on screen. */
.entries__scroll {
  flex: 1;
  min-height: 0;
  margin-top: 0.25rem;
  padding-bottom: 0.5rem;
  overflow-y: auto;
  /* Scrolling to the end of the list should not then scroll the page behind it,
     which is what a stray gesture on a phone otherwise does. */
  overscroll-behavior: contain;
  /* The list can be thrown sideways by a gesture meant for the page, and a list
     that shifts under a finger hides what was being read. */
  overflow-x: hidden;
}

/* The list can be reached and scrolled from the keyboard, which needs it to say
   so when it holds something. */
.entries__scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--radius);
}

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

/* Each entry is a card of its own rather than a row in a list, so that a long
   record is taken in a glance: the day and the length sit on one line to be
   read against each other, and the rest hangs under them. */
.entry {
  display: flex;
  align-items: stretch;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.entry + .entry {
  margin-top: 0.6rem;
}

.entry__body {
  flex: 1;
  /* Without this a long word in the family's own words would widen the card
     instead of wrapping inside it. */
  min-width: 0;
  padding: 0.8rem 0.9rem;
}

.entry__head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.6rem;
  align-items: baseline;
  justify-content: space-between;
}

.entry__date {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.entry__length {
  flex: none;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.entry__category {
  margin: 0.15rem 0 0;
  color: var(--muted);
  font-size: 0.9rem;
}

.entry__description {
  margin: 0.25rem 0 0;
  overflow-wrap: break-word;
}

/* The way back out of a wrong entry, down the right-hand side of the card and as
   tall as it, so that it is easy to find and easy to hit without aiming. It is
   a whole edge rather than a small target in the middle of the card, which on a
   phone is where thumbs land by accident.

   It is a link and not a form, so it can be opened in a new tab, and it leads to
   a screen that asks before anything is removed. */
.entry__delete {
  display: flex;
  flex: none;
  align-items: center;
  padding: 0 0.9rem;
  color: var(--danger);
  font-size: 0.9rem;
  text-decoration: none;
  white-space: nowrap;
  border-left: 1px solid var(--border);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.entry__delete:hover {
  background: var(--error-bg);
}

/* The family page, the sign-in and the bar are looked at on a phone, so these are
   the only two things that give something up for a narrow screen. The overview is
   not among them: it is worked on at a desk, where there is room for the years
   beside the families and for both lists to scroll where they are. */
@media (max-width: 45rem) {
  /* The name of the portal next to the logo is what a wide screen has room for.
     On a phone the logo alone says which portal this is, and what the bar has
     left to spend is the way out. */
  .masthead__title {
    display: none;
  }

  /* The count is large on its own account; on a phone it has to share the
     screen with the record, so it gives up some of its size rather than all of
     the list. */
  .hours__done {
    font-size: 2.1rem;
  }
}
